Summary: I want to get the generation number (i_generation
) of a file in Linux (or at least ext4) from userspace. Or, alternatively, the 'birth time' (file creation time).
I am trying to write a bidirectional file synchronisation program (aka Unison), but without a central database (easy, just store data in the synchronisation roots) and by preserving file moves (very, very hard to get right if you want to support all weird cases like moving a file out of a directory that's deleted afterwards, for example).
Having a way to uniquely identify files would ease things A LOT. I know how to get the inode and device number (via stat
), but as inodes can be re-used (I've seen this myself), I want to use a more stable unique identification.
I have read about the 'generation number' that is being used by NFS to uniquely identify files. In NFS, the (st_ino
, i_generation
) combination is used to uniquely identify file handles across reboots and server crashes (or at least to prevent re-using the same file handle leading to possible data corruption).
I haven't succeeded in getting the number, though. This ext4 documentation seems to suggest the number can be obtained from an ext4 filesystem, but I cannot get my hands on it from userspace (I'm not going to run this simple program in kernel space). See EXT4_IOC_GETVERSION
. I cannot find the appropriate header file on my system (LMDE, Debian testing). There are two options I could find:
EXT2_IOC_GETVERSION
in <linux/ext2_fs.h>
- gives EBADF
(errno
9). Maybe because I am trying to get EXT2 information from an EXT4 filesystem? Or maybe I'm doing something wrong with ioctl
, this is the first time I'm trying to use it. The file is opened correctly, because the open
call returns 3 in my case (which should be valid).the code:
#include <sys/ioctl.h>
#include <sys/fcntl.h>
#include <linux/ext2_fs.h>
#include <stdio.h>
#include <errno.h>
int main () {
int fileno = open("generation.c", O_RDONLY);
printf("fileno: %d\n", fileno);
int generation = 0;
if (ioctl(EXT2_IOC_GETVERSION, fileno, &generation)) {
printf("errno: %d\n", errno);
}
printf("generation: %d\n", generation);
}
This code gives an error (errno=9) on Ubuntu 12.04, on LMDE (Debian Testing) it gives a compiler error (EXT2_IOC_GETVERSION
not found).
An alternative would be to get the creation time (crtime
, or btime
/ birth time) of the file, but all my Googling has turned up is that this appears to be impossible to get from userspace in Linux (IIRC FreeBSD has a system call for it). I would prefer crtime
because crtime
may be more portable (to Windows) than a generation number.
I know this is going to be system-dependent. I want to have a fallback when no unique inode (or no inode at all, in the case of FAT which is common on USB sticks) can be found.
inotify
is not an option. It's a program to synchronize files after they have been modified, like rsync does in one way. Not like how Dropbox stays in the background watching for file changes.
If there is no way to get these numbers, I will also accept that as an answer (with proper documentation of course) :)
I found the solution. I swapped the fileno
and the ioctl
call (apparently C does some automatic type conversion there).
The working code looks like this:
#include <sys/ioctl.h>
#include <sys/fcntl.h>
#include <linux/fs.h>
#include <stdio.h>
#include <errno.h>
int main () {
int fileno = open("generation.c", O_RDONLY);
printf("fileno: %d\n", fileno);
int generation = 0;
if (ioctl(fileno, FS_IOC_GETVERSION, &generation)) {
printf("errno: %d\n", errno);
}
printf("generation: %d\n", generation);
}
Using FS_IOC_GETVERSION
makes the call work for most common file systems in Linux. This excludes vfat and ntfs (at least it looks like it in the kernel sources, neither vfat nor fuse are listed there), as they probably don't support generation numbers.
This fragment from coreutils (src/copy.c in the tarball) got me thinking:
/* Perform the O(1) btrfs clone operation, if possible.
Upon success, return 0. Otherwise, return -1 and set errno. */
static inline int
clone_file (int dest_fd, int src_fd)
{
#ifdef __linux__
# undef BTRFS_IOCTL_MAGIC
# define BTRFS_IOCTL_MAGIC 0x94
# undef BTRFS_IOC_CLONE
# define BTRFS_IOC_CLONE _IOW (BTRFS_IOCTL_MAGIC, 9, int)
return ioctl (dest_fd, BTRFS_IOC_CLONE, src_fd);
#else
(void) dest_fd;
(void) src_fd;
errno = ENOTSUP;
return -1;
#endif
}