diff --git a/TODO b/TODO index ea19795b7..e6af18636 100644 --- a/TODO +++ b/TODO @@ -598,3 +598,9 @@ Improvements in virt-log - Support Windows guests, see http://rwmj.wordpress.com/2011/04/17/decoding-the-windows-event-log-using-guestfish/ + +Subsecond handling in virt-diff, virt-ls +---------------------------------------- + +Handle nanoseconds properly. You should be able to specify them on +the command line and display them. diff --git a/cat/ls.c b/cat/ls.c index 43705c214..de8248e4f 100644 --- a/cat/ls.c +++ b/cat/ls.c @@ -71,7 +71,7 @@ static void output_int64 (int64_t); static void output_int64_dev (int64_t); static void output_int64_perms (int64_t); static void output_int64_size (int64_t); -static void output_int64_time (int64_t); +static void output_int64_time (int64_t secs, int64_t nsecs); static void output_int64_uid (int64_t); static void output_string (const char *); static void output_string_link (const char *); @@ -449,7 +449,7 @@ do_ls_R (const char *dir) return 0; } -static int show_file (const char *dir, const char *name, const struct guestfs_stat *stat, const struct guestfs_xattr_list *xattrs, void *unused); +static int show_file (const char *dir, const char *name, const struct guestfs_statns *stat, const struct guestfs_xattr_list *xattrs, void *unused); static int do_ls_lR (const char *dir) @@ -466,7 +466,7 @@ do_ls_lR (const char *dir) */ static int show_file (const char *dir, const char *name, - const struct guestfs_stat *stat, + const struct guestfs_statns *stat, const struct guestfs_xattr_list *xattrs, void *unused) { @@ -476,45 +476,45 @@ show_file (const char *dir, const char *name, /* Display the basic fields. */ output_start_line (); - if (is_reg (stat->mode)) + if (is_reg (stat->st_mode)) filetype = "-"; - else if (is_dir (stat->mode)) + else if (is_dir (stat->st_mode)) filetype = "d"; - else if (is_chr (stat->mode)) + else if (is_chr (stat->st_mode)) filetype = "c"; - else if (is_blk (stat->mode)) + else if (is_blk (stat->st_mode)) filetype = "b"; - else if (is_fifo (stat->mode)) + else if (is_fifo (stat->st_mode)) filetype = "p"; - else if (is_lnk (stat->mode)) + else if (is_lnk (stat->st_mode)) filetype = "l"; - else if (is_sock (stat->mode)) + else if (is_sock (stat->st_mode)) filetype = "s"; else filetype = "u"; output_string (filetype); - output_int64_perms (stat->mode & 07777); + output_int64_perms (stat->st_mode & 07777); - output_int64_size (stat->size); + output_int64_size (stat->st_size); /* Display extra fields when enabled. */ if (enable_uids) { - output_int64_uid (stat->uid); - output_int64_uid (stat->gid); + output_int64_uid (stat->st_uid); + output_int64_uid (stat->st_gid); } if (enable_times) { - output_int64_time (stat->atime); - output_int64_time (stat->mtime); - output_int64_time (stat->ctime); + output_int64_time (stat->st_atime_sec, stat->st_atime_nsec); + output_int64_time (stat->st_mtime_sec, stat->st_mtime_nsec); + output_int64_time (stat->st_ctime_sec, stat->st_ctime_nsec); } if (enable_extra_stats) { - output_int64_dev (stat->dev); - output_int64 (stat->ino); - output_int64 (stat->nlink); - output_int64_dev (stat->rdev); - output_int64 (stat->blocks); + output_int64_dev (stat->st_dev); + output_int64 (stat->st_ino); + output_int64 (stat->st_nlink); + output_int64_dev (stat->st_rdev); + output_int64 (stat->st_blocks); } /* Disabled for now -- user would definitely want these to be interpreted. @@ -524,7 +524,7 @@ show_file (const char *dir, const char *name, path = full_path (dir, name); - if (checksum && is_reg (stat->mode)) { + if (checksum && is_reg (stat->st_mode)) { csum = guestfs_checksum (g, checksum, path); if (!csum) exit (EXIT_FAILURE); @@ -534,7 +534,7 @@ show_file (const char *dir, const char *name, output_string (path); - if (is_lnk (stat->mode)) + if (is_lnk (stat->st_mode)) /* XXX Fix this for NTFS. */ link = guestfs_readlink (g, path); if (link) @@ -703,7 +703,7 @@ output_int64_perms (int64_t i) } static void -output_int64_time (int64_t i) +output_int64_time (int64_t secs, int64_t nsecs) { int r; @@ -713,19 +713,19 @@ output_int64_time (int64_t i) if (time_t_output) { switch (time_relative) { case 0: /* --time-t */ - r = printf ("%10" PRIi64, i); + r = printf ("%10" PRIi64, secs); break; case 1: /* --time-relative */ - r = printf ("%8" PRIi64, now - i); + r = printf ("%8" PRIi64, now - secs); break; case 2: /* --time-days */ default: - r = printf ("%3" PRIi64, (now - i) / 86400); + r = printf ("%3" PRIi64, (now - secs) / 86400); break; } } else { - time_t t = (time_t) i; + time_t t = (time_t) secs; char buf[64]; struct tm *tm; diff --git a/cat/visit.c b/cat/visit.c index 2347b5672..963beb8d8 100644 --- a/cat/visit.c +++ b/cat/visit.c @@ -51,11 +51,11 @@ _visit (guestfs_h *g, int depth, const char *dir, * case. */ if (depth == 0) { - CLEANUP_FREE_STAT struct guestfs_stat *stat = NULL; + CLEANUP_FREE_STATNS struct guestfs_statns *stat = NULL; CLEANUP_FREE_XATTR_LIST struct guestfs_xattr_list *xattrs = NULL; int r; - stat = guestfs_lstat (g, dir); + stat = guestfs_lstatns (g, dir); if (stat == NULL) return -1; @@ -71,14 +71,14 @@ _visit (guestfs_h *g, int depth, const char *dir, size_t i, xattrp; CLEANUP_FREE_STRING_LIST char **names = NULL; - CLEANUP_FREE_STAT_LIST struct guestfs_stat_list *stats = NULL; + CLEANUP_FREE_STAT_LIST struct guestfs_statns_list *stats = NULL; CLEANUP_FREE_XATTR_LIST struct guestfs_xattr_list *xattrs = NULL; names = guestfs_ls (g, dir); if (names == NULL) return -1; - stats = guestfs_lstatlist (g, dir, names); + stats = guestfs_lstatnslist (g, dir, names); if (stats == NULL) return -1; @@ -123,7 +123,7 @@ _visit (guestfs_h *g, int depth, const char *dir, return -1; /* Recursively call visit, but only on directories. */ - if (is_dir (stats->val[i].mode)) { + if (is_dir (stats->val[i].st_mode)) { path = full_path (dir, names[i]); if (_visit (g, depth + 1, path, f, opaque) == -1) return -1; diff --git a/cat/visit.h b/cat/visit.h index f6b538b96..a64b42e54 100644 --- a/cat/visit.h +++ b/cat/visit.h @@ -19,7 +19,7 @@ #ifndef VISIT_H #define VISIT_H -typedef int (*visitor_function) (const char *dir, const char *name, const struct guestfs_stat *stat, const struct guestfs_xattr_list *xattrs, void *opaque); +typedef int (*visitor_function) (const char *dir, const char *name, const struct guestfs_statns *stat, const struct guestfs_xattr_list *xattrs, void *opaque); extern int visit (guestfs_h *g, const char *dir, visitor_function f, void *opaque); diff --git a/configure.ac b/configure.ac index 0b2c0e01b..01f1b3bb5 100644 --- a/configure.ac +++ b/configure.ac @@ -270,6 +270,12 @@ dnl Check if stat has the required fields. AC_STRUCT_ST_BLOCKS AC_CHECK_MEMBER([struct stat.st_blksize],[ AC_DEFINE([HAVE_STRUCT_STAT_ST_BLKSIZE],[1],[Define to 1 if 'st_blksize' is a member of 'struct stat'.])]) +AC_CHECK_MEMBER([struct stat.st_atim.tv_nsec],[ + AC_DEFINE([HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC],[1],[Define to 1 if 'st_mtim.tv_nsec' is a member of 'struct stat'.])]) +AC_CHECK_MEMBER([struct stat.st_mtim.tv_nsec],[ + AC_DEFINE([HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC],[1],[Define to 1 if 'st_mtim.tv_nsec' is a member of 'struct stat'.])]) +AC_CHECK_MEMBER([struct stat.st_ctim.tv_nsec],[ + AC_DEFINE([HAVE_STRUCT_STAT_ST_CTIM_TV_NSEC],[1],[Define to 1 if 'st_mtim.tv_nsec' is a member of 'struct stat'.])]) dnl Define a C symbol for the host CPU architecture. AC_DEFINE_UNQUOTED([host_cpu],["$host_cpu"],[Host architecture.]) diff --git a/daemon/stat.c b/daemon/stat.c index 939fe08eb..a7849141e 100644 --- a/daemon/stat.c +++ b/daemon/stat.c @@ -30,11 +30,64 @@ #include "daemon.h" #include "actions.h" -guestfs_int_stat * -do_stat (const char *path) +static guestfs_int_statns * +stat_to_statns (guestfs_int_statns *ret, const struct stat *statbuf) +{ + if (ret == NULL) { + ret = malloc (sizeof *ret); + if (ret == NULL) { + reply_with_perror ("malloc"); + return NULL; + } + } + + ret->st_dev = statbuf->st_dev; + ret->st_ino = statbuf->st_ino; + ret->st_mode = statbuf->st_mode; + ret->st_nlink = statbuf->st_nlink; + ret->st_uid = statbuf->st_uid; + ret->st_gid = statbuf->st_gid; + ret->st_rdev = statbuf->st_rdev; + ret->st_size = statbuf->st_size; +#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE + ret->st_blksize = statbuf->st_blksize; +#else + ret->st_blksize = -1; +#endif +#ifdef HAVE_STRUCT_STAT_ST_BLOCKS + ret->st_blocks = statbuf->st_blocks; +#else + ret->st_blocks = -1; +#endif + ret->st_atime_sec = statbuf->st_atime; +#ifdef HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC + ret->st_atime_nsec = statbuf->st_atim.tv_nsec; +#else + ret->st_atime_nsec = 0; +#endif + ret->st_mtime_sec = statbuf->st_mtime; +#ifdef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC + ret->st_mtime_nsec = statbuf->st_mtim.tv_nsec; +#else + ret->st_mtime_nsec = 0; +#endif + ret->st_ctime_sec = statbuf->st_ctime; +#ifdef HAVE_STRUCT_STAT_ST_CTIM_TV_NSEC + ret->st_ctime_nsec = statbuf->st_ctim.tv_nsec; +#else + ret->st_ctime_nsec = 0; +#endif + + ret->st_spare1 = ret->st_spare2 = ret->st_spare3 = + ret->st_spare4 = ret->st_spare5 = ret->st_spare6 = 0; + + return ret; +} + +guestfs_int_statns * +do_statns (const char *path) { int r; - guestfs_int_stat *ret; struct stat statbuf; CHROOT_IN; @@ -46,42 +99,13 @@ do_stat (const char *path) return NULL; } - ret = malloc (sizeof *ret); - if (ret == NULL) { - reply_with_perror ("malloc"); - return NULL; - } - - ret->dev = statbuf.st_dev; - ret->ino = statbuf.st_ino; - ret->mode = statbuf.st_mode; - ret->nlink = statbuf.st_nlink; - ret->uid = statbuf.st_uid; - ret->gid = statbuf.st_gid; - ret->rdev = statbuf.st_rdev; - ret->size = statbuf.st_size; -#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE - ret->blksize = statbuf.st_blksize; -#else - ret->blksize = -1; -#endif -#ifdef HAVE_STRUCT_STAT_ST_BLOCKS - ret->blocks = statbuf.st_blocks; -#else - ret->blocks = -1; -#endif - ret->atime = statbuf.st_atime; - ret->mtime = statbuf.st_mtime; - ret->ctime = statbuf.st_ctime; - - return ret; + return stat_to_statns (NULL, &statbuf); } -guestfs_int_stat * -do_lstat (const char *path) +guestfs_int_statns * +do_lstatns (const char *path) { int r; - guestfs_int_stat *ret; struct stat statbuf; CHROOT_IN; @@ -93,42 +117,14 @@ do_lstat (const char *path) return NULL; } - ret = malloc (sizeof *ret); - if (ret == NULL) { - reply_with_perror ("malloc"); - return NULL; - } - - ret->dev = statbuf.st_dev; - ret->ino = statbuf.st_ino; - ret->mode = statbuf.st_mode; - ret->nlink = statbuf.st_nlink; - ret->uid = statbuf.st_uid; - ret->gid = statbuf.st_gid; - ret->rdev = statbuf.st_rdev; - ret->size = statbuf.st_size; -#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE - ret->blksize = statbuf.st_blksize; -#else - ret->blksize = -1; -#endif -#ifdef HAVE_STRUCT_STAT_ST_BLOCKS - ret->blocks = statbuf.st_blocks; -#else - ret->blocks = -1; -#endif - ret->atime = statbuf.st_atime; - ret->mtime = statbuf.st_mtime; - ret->ctime = statbuf.st_ctime; - - return ret; + return stat_to_statns (NULL, &statbuf); } -guestfs_int_stat_list * -do_internal_lstatlist (const char *path, char *const *names) +guestfs_int_statns_list * +do_internal_lstatnslist (const char *path, char *const *names) { int path_fd; - guestfs_int_stat_list *ret; + guestfs_int_statns_list *ret; size_t i, nr_names; nr_names = count_strings (names); @@ -138,9 +134,10 @@ do_internal_lstatlist (const char *path, char *const *names) reply_with_perror ("malloc"); return NULL; } - ret->guestfs_int_stat_list_len = nr_names; - ret->guestfs_int_stat_list_val = calloc (nr_names, sizeof (guestfs_int_stat)); - if (ret->guestfs_int_stat_list_val == NULL) { + ret->guestfs_int_statns_list_len = nr_names; + ret->guestfs_int_statns_list_val = + calloc (nr_names, sizeof (guestfs_int_statns)); + if (ret->guestfs_int_statns_list_val == NULL) { reply_with_perror ("malloc"); free (ret); return NULL; @@ -152,7 +149,7 @@ do_internal_lstatlist (const char *path, char *const *names) if (path_fd == -1) { reply_with_perror ("%s", path); - free (ret->guestfs_int_stat_list_val); + free (ret->guestfs_int_statns_list_val); free (ret); return NULL; } @@ -163,35 +160,14 @@ do_internal_lstatlist (const char *path, char *const *names) r = fstatat (path_fd, names[i], &statbuf, AT_SYMLINK_NOFOLLOW); if (r == -1) - ret->guestfs_int_stat_list_val[i].ino = -1; - else { - ret->guestfs_int_stat_list_val[i].dev = statbuf.st_dev; - ret->guestfs_int_stat_list_val[i].ino = statbuf.st_ino; - ret->guestfs_int_stat_list_val[i].mode = statbuf.st_mode; - ret->guestfs_int_stat_list_val[i].nlink = statbuf.st_nlink; - ret->guestfs_int_stat_list_val[i].uid = statbuf.st_uid; - ret->guestfs_int_stat_list_val[i].gid = statbuf.st_gid; - ret->guestfs_int_stat_list_val[i].rdev = statbuf.st_rdev; - ret->guestfs_int_stat_list_val[i].size = statbuf.st_size; -#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE - ret->guestfs_int_stat_list_val[i].blksize = statbuf.st_blksize; -#else - ret->guestfs_int_stat_list_val[i].blksize = -1; -#endif -#ifdef HAVE_STRUCT_STAT_ST_BLOCKS - ret->guestfs_int_stat_list_val[i].blocks = statbuf.st_blocks; -#else - ret->guestfs_int_stat_list_val[i].blocks = -1; -#endif - ret->guestfs_int_stat_list_val[i].atime = statbuf.st_atime; - ret->guestfs_int_stat_list_val[i].mtime = statbuf.st_mtime; - ret->guestfs_int_stat_list_val[i].ctime = statbuf.st_ctime; - } + ret->guestfs_int_statns_list_val[i].st_ino = -1; + else + stat_to_statns (&ret->guestfs_int_statns_list_val[i], &statbuf); } if (close (path_fd) == -1) { reply_with_perror ("close: %s", path); - free (ret->guestfs_int_stat_list_val); + free (ret->guestfs_int_statns_list_val); free (ret); return NULL; } diff --git a/diff/diff.c b/diff/diff.c index 16c970b97..af4e1797a 100644 --- a/diff/diff.c +++ b/diff/diff.c @@ -78,7 +78,7 @@ static void output_int64 (int64_t); static void output_int64_dev (int64_t); static void output_int64_perms (int64_t); static void output_int64_size (int64_t); -static void output_int64_time (int64_t); +static void output_int64_time (int64_t secs, int64_t nsecs); static void output_int64_uid (int64_t); static void output_string (const char *); static void output_string_link (const char *); @@ -398,7 +398,7 @@ struct tree { struct file { char *path; - struct guestfs_stat *stat; + struct guestfs_statns *stat; struct guestfs_xattr_list *xattrs; char *csum; /* Checksum. If NULL, use file times and size. */ }; @@ -410,7 +410,7 @@ free_tree (struct tree *t) for (i = 0; i < t->nr_files; ++i) { free (t->files[i].path); - guestfs_free_stat (t->files[i].stat); + guestfs_free_statns (t->files[i].stat); guestfs_free_xattr_list (t->files[i].xattrs); free (t->files[i].csum); } @@ -420,7 +420,7 @@ free_tree (struct tree *t) free (t); } -static int visit_entry (const char *dir, const char *name, const struct guestfs_stat *stat, const struct guestfs_xattr_list *xattrs, void *vt); +static int visit_entry (const char *dir, const char *name, const struct guestfs_statns *stat, const struct guestfs_xattr_list *xattrs, void *vt); static struct tree * visit_guest (guestfs_h *g) @@ -454,13 +454,13 @@ visit_guest (guestfs_h *g) */ static int visit_entry (const char *dir, const char *name, - const struct guestfs_stat *stat_orig, + const struct guestfs_statns *stat_orig, const struct guestfs_xattr_list *xattrs_orig, void *vt) { struct tree *t = vt; char *path = NULL, *csum = NULL; - struct guestfs_stat *stat = NULL; + struct guestfs_statns *stat = NULL; struct guestfs_xattr_list *xattrs = NULL; size_t i; @@ -469,7 +469,7 @@ visit_entry (const char *dir, const char *name, /* Copy the stats and xattrs because the visit function will * free them after we return. */ - stat = guestfs_copy_stat (stat_orig); + stat = guestfs_copy_statns (stat_orig); if (stat == NULL) { perror ("guestfs_copy_stat"); goto error; @@ -480,7 +480,7 @@ visit_entry (const char *dir, const char *name, goto error; } - if (checksum && is_reg (stat->mode)) { + if (checksum && is_reg (stat->st_mode)) { csum = guestfs_checksum (t->g, checksum, path); if (!csum) goto error; @@ -488,19 +488,20 @@ visit_entry (const char *dir, const char *name, /* If --atime option was NOT passed, flatten the atime field. */ if (!atime) - stat->atime = 0; + stat->st_atime_sec = 0; /* If --dir-links option was NOT passed, flatten nlink field in * directories. */ - if (!dir_links && is_dir (stat->mode)) - stat->nlink = 0; + if (!dir_links && is_dir (stat->st_mode)) + stat->st_nlink = 0; /* If --dir-times option was NOT passed, flatten time fields in * directories. */ - if (!dir_times && is_dir (stat->mode)) - stat->atime = stat->mtime = stat->ctime = 0; + if (!dir_times && is_dir (stat->st_mode)) + stat->st_atime_sec = stat->st_mtime_sec = stat->st_ctime_sec = + stat->st_atime_nsec = stat->st_mtime_nsec = stat->st_ctime_nsec = 0; /* Add the pathname and stats to the list. */ i = t->nr_files++; @@ -535,7 +536,7 @@ visit_entry (const char *dir, const char *name, error: free (path); free (csum); - guestfs_free_stat (stat); + guestfs_free_statns (stat); guestfs_free_xattr_list (xattrs); return -1; } @@ -622,7 +623,7 @@ compare_stats (struct file *file1, struct file *file2) { int r; - r = guestfs_compare_stat (file1->stat, file2->stat); + r = guestfs_compare_statns (file1->stat, file2->stat); if (r != 0) return r; @@ -640,10 +641,10 @@ changed (guestfs_h *g1, struct file *file1, { /* Did file content change? */ if (cst != 0 || - (is_reg (file1->stat->mode) && is_reg (file2->stat->mode) && - (file1->stat->mtime != file2->stat->mtime || - file1->stat->ctime != file2->stat->ctime || - file1->stat->size != file2->stat->size))) { + (is_reg (file1->stat->st_mode) && is_reg (file2->stat->st_mode) && + (file1->stat->st_mtime_sec != file2->stat->st_mtime_sec || + file1->stat->st_ctime_sec != file2->stat->st_ctime_sec || + file1->stat->st_size != file2->stat->st_size))) { output_start_line (); output_string ("="); output_file (g1, file1); @@ -673,19 +674,19 @@ changed (guestfs_h *g1, struct file *file1, output_string ("changed:"); #define COMPARE_STAT(n) \ if (file1->stat->n != file2->stat->n) output_string (#n) - COMPARE_STAT (dev); - COMPARE_STAT (ino); - COMPARE_STAT (mode); - COMPARE_STAT (nlink); - COMPARE_STAT (uid); - COMPARE_STAT (gid); - COMPARE_STAT (rdev); - COMPARE_STAT (size); - COMPARE_STAT (blksize); - COMPARE_STAT (blocks); - COMPARE_STAT (atime); - COMPARE_STAT (mtime); - COMPARE_STAT (ctime); + COMPARE_STAT (st_dev); + COMPARE_STAT (st_ino); + COMPARE_STAT (st_mode); + COMPARE_STAT (st_nlink); + COMPARE_STAT (st_uid); + COMPARE_STAT (st_gid); + COMPARE_STAT (st_rdev); + COMPARE_STAT (st_size); + COMPARE_STAT (st_blksize); + COMPARE_STAT (st_blocks); + COMPARE_STAT (st_atime_sec); + COMPARE_STAT (st_mtime_sec); + COMPARE_STAT (st_ctime_sec); #undef COMPARE_STAT if (guestfs_compare_xattr_list (file1->xattrs, file2->xattrs)) output_string ("xattrs"); @@ -701,8 +702,8 @@ diff (struct file *file1, guestfs_h *g1, struct file *file2, guestfs_h *g2) CLEANUP_FREE char *tmpd, *tmpda = NULL, *tmpdb = NULL, *cmd = NULL; int r; - assert (is_reg (file1->stat->mode)); - assert (is_reg (file2->stat->mode)); + assert (is_reg (file1->stat->st_mode)); + assert (is_reg (file2->stat->st_mode)); if (asprintf (&tmpd, "%s/virtdiffXXXXXX", tmpdir) < 0) { perror ("asprintf"); @@ -755,47 +756,47 @@ output_file (guestfs_h *g, struct file *file) size_t i; CLEANUP_FREE char *link = NULL; - if (is_reg (file->stat->mode)) + if (is_reg (file->stat->st_mode)) filetype = "-"; - else if (is_dir (file->stat->mode)) + else if (is_dir (file->stat->st_mode)) filetype = "d"; - else if (is_chr (file->stat->mode)) + else if (is_chr (file->stat->st_mode)) filetype = "c"; - else if (is_blk (file->stat->mode)) + else if (is_blk (file->stat->st_mode)) filetype = "b"; - else if (is_fifo (file->stat->mode)) + else if (is_fifo (file->stat->st_mode)) filetype = "p"; - else if (is_lnk (file->stat->mode)) + else if (is_lnk (file->stat->st_mode)) filetype = "l"; - else if (is_sock (file->stat->mode)) + else if (is_sock (file->stat->st_mode)) filetype = "s"; else filetype = "u"; output_string (filetype); - output_int64_perms (file->stat->mode & 07777); + output_int64_perms (file->stat->st_mode & 07777); - output_int64_size (file->stat->size); + output_int64_size (file->stat->st_size); /* Display extra fields when enabled. */ if (enable_uids) { - output_int64_uid (file->stat->uid); - output_int64_uid (file->stat->gid); + output_int64_uid (file->stat->st_uid); + output_int64_uid (file->stat->st_gid); } if (enable_times) { if (atime) - output_int64_time (file->stat->atime); - output_int64_time (file->stat->mtime); - output_int64_time (file->stat->ctime); + output_int64_time (file->stat->st_atime_sec, file->stat->st_atime_nsec); + output_int64_time (file->stat->st_mtime_sec, file->stat->st_mtime_nsec); + output_int64_time (file->stat->st_ctime_sec, file->stat->st_ctime_nsec); } if (enable_extra_stats) { - output_int64_dev (file->stat->dev); - output_int64 (file->stat->ino); - output_int64 (file->stat->nlink); - output_int64_dev (file->stat->rdev); - output_int64 (file->stat->blocks); + output_int64_dev (file->stat->st_dev); + output_int64 (file->stat->st_ino); + output_int64 (file->stat->st_nlink); + output_int64_dev (file->stat->st_rdev); + output_int64 (file->stat->st_blocks); } if (file->csum) @@ -803,7 +804,7 @@ output_file (guestfs_h *g, struct file *file) output_string (file->path); - if (is_lnk (file->stat->mode)) { + if (is_lnk (file->stat->st_mode)) { /* XXX Fix this for NTFS. */ link = guestfs_readlink (g, file->path); if (link) @@ -1056,7 +1057,7 @@ output_int64_perms (int64_t i) } static void -output_int64_time (int64_t i) +output_int64_time (int64_t secs, int64_t nsecs) { int r; @@ -1066,19 +1067,19 @@ output_int64_time (int64_t i) if (time_t_output) { switch (time_relative) { case 0: /* --time-t */ - r = printf ("%10" PRIi64, i); + r = printf ("%10" PRIi64, secs); break; case 1: /* --time-relative */ - r = printf ("%8" PRIi64, now - i); + r = printf ("%8" PRIi64, now - secs); break; case 2: /* --time-days */ default: - r = printf ("%3" PRIi64, (now - i) / 86400); + r = printf ("%3" PRIi64, (now - secs) / 86400); break; } } else { - time_t t = (time_t) i; + time_t t = (time_t) secs; char buf[64]; struct tm *tm; diff --git a/fuse/test-fuse.c b/fuse/test-fuse.c index dda6fdeb3..2876dc4de 100644 --- a/fuse/test-fuse.c +++ b/fuse/test-fuse.c @@ -247,9 +247,9 @@ test_fuse (void) char buf[128]; ssize_t r; unsigned u, u1; -#if 0 int fd; struct timeval tv[2]; +#if 0 struct timespec ts[2]; #endif acl_t acl; @@ -544,7 +544,6 @@ test_fuse (void) return -1; } -#if 0 STAGE ("checking utimes"); fd = open ("timestamp", O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY|O_CLOEXEC, 0644); @@ -574,7 +573,6 @@ test_fuse (void) (int) statbuf.st_mtime, (int) statbuf.st_mtim.tv_nsec); return -1; } -#endif #if 0 /* Does not work! See https://bugzilla.redhat.com/show_bug.cgi?id=1144766 */ diff --git a/generator/actions.ml b/generator/actions.ml index 73dcd33ec..778219828 100644 --- a/generator/actions.ml +++ b/generator/actions.ml @@ -2605,6 +2605,7 @@ See also C." }; { defaults with name = "lstatlist"; style = RStructList ("statbufs", "stat"), [Pathname "path"; StringList "names"], []; + deprecated_by = Some "lstatnslist"; shortdesc = "lstat on multiple files"; longdesc = "\ This call allows you to perform the C operation @@ -2613,7 +2614,26 @@ C is the list of files from this directory. On return you get a list of stat structs, with a one-to-one correspondence to the C list. If any name did not exist -or could not be lstat'd, then the C field of that structure +or could not be lstat'd, then the C field of that structure +is set to C<-1>. + +This call is intended for programs that want to efficiently +list a directory contents without making many round-trips. +See also C for a similarly efficient call +for getting extended attributes." }; + + { defaults with + name = "lstatnslist"; + style = RStructList ("statbufs", "statns"), [Pathname "path"; StringList "names"], []; + shortdesc = "lstat on multiple files"; + longdesc = "\ +This call allows you to perform the C operation +on multiple files, where all files are in the directory C. +C is the list of files from this directory. + +On return you get a list of stat structs, with a one-to-one +correspondence to the C list. If any name did not exist +or could not be lstat'd, then the C field of that structure is set to C<-1>. This call is intended for programs that want to efficiently @@ -3223,6 +3243,38 @@ This call returns the number of strings which were removed See L, L." }; + { defaults with + name = "stat"; + style = RStruct ("statbuf", "stat"), [Pathname "path"], []; + deprecated_by = Some "statns"; + tests = [ + InitISOFS, Always, TestResult ( + [["stat"; "/empty"]], "ret->size == 0"), [] + ]; + shortdesc = "get file information"; + longdesc = "\ +Returns file information for the given C. + +This is the same as the C system call." }; + + { defaults with + name = "lstat"; + style = RStruct ("statbuf", "stat"), [Pathname "path"], []; + deprecated_by = Some "lstatns"; + tests = [ + InitISOFS, Always, TestResult ( + [["lstat"; "/empty"]], "ret->size == 0"), [] + ]; + shortdesc = "get file information for a symbolic link"; + longdesc = "\ +Returns file information for the given C. + +This is the same as C except that if C +is a symbolic link, then the link is stat-ed, not the file it +refers to. + +This is the same as the C system call." }; + ] (* daemon_functions are any functions which cause some action @@ -4346,38 +4398,6 @@ result into a list of lines. See also: C" }; - { defaults with - name = "stat"; - style = RStruct ("statbuf", "stat"), [Pathname "path"], []; - proc_nr = Some 52; - tests = [ - InitISOFS, Always, TestResult ( - [["stat"; "/empty"]], "ret->size == 0"), [] - ]; - shortdesc = "get file information"; - longdesc = "\ -Returns file information for the given C. - -This is the same as the C system call." }; - - { defaults with - name = "lstat"; - style = RStruct ("statbuf", "stat"), [Pathname "path"], []; - proc_nr = Some 53; - tests = [ - InitISOFS, Always, TestResult ( - [["lstat"; "/empty"]], "ret->size == 0"), [] - ]; - shortdesc = "get file information for a symbolic link"; - longdesc = "\ -Returns file information for the given C. - -This is the same as C except that if C -is a symbolic link, then the link is stat-ed, not the file it -refers to. - -This is the same as the C system call." }; - { defaults with name = "statvfs"; style = RStruct ("statbuf", "statvfs"), [Pathname "path"], []; @@ -7492,30 +7512,6 @@ Only numeric uid and gid are supported. If you want to use names, you will need to locate and parse the password file yourself (Augeas support makes this relatively easy)." }; - { defaults with - name = "internal_lstatlist"; - style = RStructList ("statbufs", "stat"), [Pathname "path"; StringList "names"], []; - proc_nr = Some 204; - visibility = VInternal; - shortdesc = "lstat on multiple files"; - longdesc = "\ -This call allows you to perform the C operation -on multiple files, where all files are in the directory C. -C is the list of files from this directory. - -On return you get a list of stat structs, with a one-to-one -correspondence to the C list. If any name did not exist -or could not be lstat'd, then the C field of that structure -is set to C<-1>. - -This call is intended for programs that want to efficiently -list a directory contents without making many round-trips. -See also C for a similarly efficient call -for getting extended attributes. Very long directory listings -might cause the protocol message size to be exceeded, causing -this call to fail. The caller must split up such requests -into smaller groups of names." }; - { defaults with name = "internal_lxattrlist"; style = RStructList ("xattrs", "xattr"), [Pathname "path"; StringList "names"], []; @@ -11942,6 +11938,47 @@ New (SVR4) portable format with a checksum. longdesc = "\ Get the realtime (wallclock) timestamp of the current journal entry." }; + { defaults with + name = "statns"; + style = RStruct ("statbuf", "statns"), [Pathname "path"], []; + proc_nr = Some 421; + tests = [ + InitISOFS, Always, TestResult ( + [["statns"; "/empty"]], "ret->st_size == 0"), [] + ]; + shortdesc = "get file information"; + longdesc = "\ +Returns file information for the given C. + +This is the same as the C system call." }; + + { defaults with + name = "lstatns"; + style = RStruct ("statbuf", "statns"), [Pathname "path"], []; + proc_nr = Some 422; + tests = [ + InitISOFS, Always, TestResult ( + [["lstatns"; "/empty"]], "ret->st_size == 0"), [] + ]; + shortdesc = "get file information for a symbolic link"; + longdesc = "\ +Returns file information for the given C. + +This is the same as C except that if C +is a symbolic link, then the link is stat-ed, not the file it +refers to. + +This is the same as the C system call." }; + + { defaults with + name = "internal_lstatnslist"; + style = RStructList ("statbufs", "statns"), [Pathname "path"; StringList "names"], []; + proc_nr = Some 423; + visibility = VInternal; + shortdesc = "lstat on multiple files"; + longdesc = "\ +This is the internal call which implements C." }; + ] (* Non-API meta-commands available only in guestfish. diff --git a/generator/structs.ml b/generator/structs.ml index 65c78b20e..578ebb79f 100644 --- a/generator/structs.ml +++ b/generator/structs.ml @@ -143,6 +143,36 @@ let structs = [ "ctime", FInt64; ]; s_camel_name = "Stat" }; + (* Because we omitted the nanosecond fields from the above struct, + * we also have this: + *) + { defaults with + s_name = "statns"; + s_cols = [ + "st_dev", FInt64; + "st_ino", FInt64; + "st_mode", FInt64; + "st_nlink", FInt64; + "st_uid", FInt64; + "st_gid", FInt64; + "st_rdev", FInt64; + "st_size", FInt64; + "st_blksize", FInt64; + "st_blocks", FInt64; + "st_atime_sec", FInt64; + "st_atime_nsec", FInt64; + "st_mtime_sec", FInt64; + "st_mtime_nsec", FInt64; + "st_ctime_sec", FInt64; + "st_ctime_nsec", FInt64; + "st_spare1", FInt64; + "st_spare2", FInt64; + "st_spare3", FInt64; + "st_spare4", FInt64; + "st_spare5", FInt64; + "st_spare6", FInt64; + ]; + s_camel_name = "StatNS" }; { defaults with s_name = "statvfs"; s_cols = [ diff --git a/gobject/Makefile.inc b/gobject/Makefile.inc index ed1ff3bc3..c93dace6c 100644 --- a/gobject/Makefile.inc +++ b/gobject/Makefile.inc @@ -38,6 +38,7 @@ guestfs_gobject_headers= \ include/guestfs-gobject/struct-mdstat.h \ include/guestfs-gobject/struct-partition.h \ include/guestfs-gobject/struct-stat.h \ + include/guestfs-gobject/struct-statns.h \ include/guestfs-gobject/struct-statvfs.h \ include/guestfs-gobject/struct-utsname.h \ include/guestfs-gobject/struct-version.h \ @@ -115,6 +116,7 @@ guestfs_gobject_sources= \ src/struct-mdstat.c \ src/struct-partition.c \ src/struct-stat.c \ + src/struct-statns.c \ src/struct-statvfs.c \ src/struct-utsname.c \ src/struct-version.c \ diff --git a/java/Makefile.inc b/java/Makefile.inc index 731e78210..614caaa5f 100644 --- a/java/Makefile.inc +++ b/java/Makefile.inc @@ -34,6 +34,7 @@ java_built_sources = \ com/redhat/et/libguestfs/PV.java \ com/redhat/et/libguestfs/Partition.java \ com/redhat/et/libguestfs/Stat.java \ + com/redhat/et/libguestfs/StatNS.java \ com/redhat/et/libguestfs/StatVFS.java \ com/redhat/et/libguestfs/UTSName.java \ com/redhat/et/libguestfs/VG.java \ diff --git a/java/com/redhat/et/libguestfs/.gitignore b/java/com/redhat/et/libguestfs/.gitignore index 00bcec968..4882c9672 100644 --- a/java/com/redhat/et/libguestfs/.gitignore +++ b/java/com/redhat/et/libguestfs/.gitignore @@ -12,6 +12,7 @@ MDStat.java PV.java Partition.java Stat.java +StatNS.java StatVFS.java UTSName.java VG.java diff --git a/po/POTFILES b/po/POTFILES index add74b635..aec8c62f8 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -238,6 +238,7 @@ gobject/src/struct-lvm_vg.c gobject/src/struct-mdstat.c gobject/src/struct-partition.c gobject/src/struct-stat.c +gobject/src/struct-statns.c gobject/src/struct-statvfs.c gobject/src/struct-utsname.c gobject/src/struct-version.c diff --git a/src/MAX_PROC_NR b/src/MAX_PROC_NR index 816d01be5..572141363 100644 --- a/src/MAX_PROC_NR +++ b/src/MAX_PROC_NR @@ -1 +1 @@ -420 +423 diff --git a/src/file.c b/src/file.c index d21a61d29..91f794763 100644 --- a/src/file.c +++ b/src/file.c @@ -377,33 +377,33 @@ guestfs__write_append (guestfs_h *g, const char *path, return write_or_append (g, path, content, size, 1); } -#define LSTATLIST_MAX 1000 +#define LSTATNSLIST_MAX 1000 -struct guestfs_stat_list * -guestfs__lstatlist (guestfs_h *g, const char *dir, char * const*names) +struct guestfs_statns_list * +guestfs__lstatnslist (guestfs_h *g, const char *dir, char * const*names) { size_t len = guestfs___count_strings (names); size_t old_len; - struct guestfs_stat_list *ret; + struct guestfs_statns_list *ret; ret = safe_malloc (g, sizeof *ret); ret->len = 0; ret->val = NULL; while (len > 0) { - CLEANUP_FREE_STAT_LIST struct guestfs_stat_list *stats = NULL; + CLEANUP_FREE_STATNS_LIST struct guestfs_statns_list *stats = NULL; /* Note we don't need to free up the strings because take_strings * does not do a deep copy. */ - CLEANUP_FREE char **first = take_strings (g, names, LSTATLIST_MAX, &names); + CLEANUP_FREE char **first = take_strings (g, names, LSTATNSLIST_MAX, &names); - len = len <= LSTATLIST_MAX ? 0 : len - LSTATLIST_MAX; + len = len <= LSTATNSLIST_MAX ? 0 : len - LSTATNSLIST_MAX; - stats = guestfs_internal_lstatlist (g, dir, first); + stats = guestfs_internal_lstatnslist (g, dir, first); if (stats == NULL) { - guestfs_free_stat_list (ret); + guestfs_free_statns_list (ret); return NULL; } @@ -411,9 +411,9 @@ guestfs__lstatlist (guestfs_h *g, const char *dir, char * const*names) old_len = ret->len; ret->len += stats->len; ret->val = safe_realloc (g, ret->val, - ret->len * sizeof (struct guestfs_stat)); + ret->len * sizeof (struct guestfs_statns)); memcpy (&ret->val[old_len], stats->val, - stats->len * sizeof (struct guestfs_stat)); + stats->len * sizeof (struct guestfs_statns)); } return ret; @@ -602,3 +602,72 @@ guestfs__ls (guestfs_h *g, const char *directory) close (fd); return NULL; } + +static void +statns_to_old_stat (struct guestfs_statns *a, struct guestfs_stat *r) +{ + r->dev = a->st_dev; + r->ino = a->st_ino; + r->mode = a->st_mode; + r->nlink = a->st_nlink; + r->uid = a->st_uid; + r->gid = a->st_gid; + r->rdev = a->st_rdev; + r->size = a->st_size; + r->blksize = a->st_blksize; + r->blocks = a->st_blocks; + r->atime = a->st_atime_sec; + r->mtime = a->st_mtime_sec; + r->ctime = a->st_ctime_sec; +} + +struct guestfs_stat * +guestfs__stat (guestfs_h *g, const char *path) +{ + CLEANUP_FREE_STATNS struct guestfs_statns *r; + struct guestfs_stat *ret; + + r = guestfs_statns (g, path); + if (r == NULL) + return NULL; + + ret = safe_malloc (g, sizeof *ret); + statns_to_old_stat (r, ret); + return ret; /* caller frees */ +} + +struct guestfs_stat * +guestfs__lstat (guestfs_h *g, const char *path) +{ + CLEANUP_FREE_STATNS struct guestfs_statns *r; + struct guestfs_stat *ret; + + r = guestfs_lstatns (g, path); + if (r == NULL) + return NULL; + + ret = safe_malloc (g, sizeof *ret); + statns_to_old_stat (r, ret); + return ret; /* caller frees */ +} + +struct guestfs_stat_list * +guestfs__lstatlist (guestfs_h *g, const char *dir, char * const*names) +{ + CLEANUP_FREE_STATNS_LIST struct guestfs_statns_list *r; + struct guestfs_stat_list *ret; + size_t i; + + r = guestfs_lstatnslist (g, dir, names); + if (r == NULL) + return NULL; + + ret = safe_malloc (g, sizeof *ret); + ret->len = r->len; + ret->val = safe_calloc (g, r->len, sizeof (struct guestfs_stat)); + + for (i = 0; i < r->len; ++i) + statns_to_old_stat (&r->val[i], &ret->val[i]); + + return ret; +} diff --git a/src/fuse.c b/src/fuse.c index 00f9092e9..08a8784e5 100644 --- a/src/fuse.c +++ b/src/fuse.c @@ -165,7 +165,7 @@ mount_local_readdir (const char *path, void *buf, fuse_fill_dir_t filler, */ names = malloc ((ents->len + 1) * sizeof (char *)); if (names) { - CLEANUP_FREE_STAT_LIST struct guestfs_stat_list *ss = NULL; + CLEANUP_FREE_STATNS_LIST struct guestfs_statns_list *ss = NULL; CLEANUP_FREE_XATTR_LIST struct guestfs_xattr_list *xattrs = NULL; char **links; @@ -173,26 +173,35 @@ mount_local_readdir (const char *path, void *buf, fuse_fill_dir_t filler, names[i] = ents->val[i].name; names[i] = NULL; - ss = guestfs_lstatlist (g, path, names); + ss = guestfs_lstatnslist (g, path, names); if (ss) { for (i = 0; i < ss->len; ++i) { - if (ss->val[i].ino >= 0) { + if (ss->val[i].st_ino >= 0) { struct stat statbuf; memset (&statbuf, 0, sizeof statbuf); - statbuf.st_dev = ss->val[i].dev; - statbuf.st_ino = ss->val[i].ino; - statbuf.st_mode = ss->val[i].mode; - statbuf.st_nlink = ss->val[i].nlink; - statbuf.st_uid = ss->val[i].uid; - statbuf.st_gid = ss->val[i].gid; - statbuf.st_rdev = ss->val[i].rdev; - statbuf.st_size = ss->val[i].size; - statbuf.st_blksize = ss->val[i].blksize; - statbuf.st_blocks = ss->val[i].blocks; - statbuf.st_atime = ss->val[i].atime; - statbuf.st_mtime = ss->val[i].mtime; - statbuf.st_ctime = ss->val[i].ctime; + statbuf.st_dev = ss->val[i].st_dev; + statbuf.st_ino = ss->val[i].st_ino; + statbuf.st_mode = ss->val[i].st_mode; + statbuf.st_nlink = ss->val[i].st_nlink; + statbuf.st_uid = ss->val[i].st_uid; + statbuf.st_gid = ss->val[i].st_gid; + statbuf.st_rdev = ss->val[i].st_rdev; + statbuf.st_size = ss->val[i].st_size; + statbuf.st_blksize = ss->val[i].st_blksize; + statbuf.st_blocks = ss->val[i].st_blocks; + statbuf.st_atime = ss->val[i].st_atime_sec; +#ifdef HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC + statbuf.st_atim.tv_nsec = ss->val[i].st_atime_nsec; +#endif + statbuf.st_mtime = ss->val[i].st_mtime_sec; +#ifdef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC + statbuf.st_mtim.tv_nsec = ss->val[i].st_mtime_nsec; +#endif + statbuf.st_ctime = ss->val[i].st_ctime_sec; +#ifdef HAVE_STRUCT_STAT_ST_CTIM_TV_NSEC + statbuf.st_ctim.tv_nsec = ss->val[i].st_ctime_nsec; +#endif lsc_insert (g, path, names[i], now, &statbuf); } @@ -246,7 +255,7 @@ static int mount_local_getattr (const char *path, struct stat *statbuf) { const struct stat *buf; - CLEANUP_FREE_STAT struct guestfs_stat *r = NULL; + CLEANUP_FREE_STAT struct guestfs_statns *r = NULL; DECL_G (); DEBUG_CALL ("%s, %p", path, statbuf); @@ -256,24 +265,33 @@ mount_local_getattr (const char *path, struct stat *statbuf) return 0; } - r = guestfs_lstat (g, path); + r = guestfs_lstatns (g, path); if (r == NULL) RETURN_ERRNO; memset (statbuf, 0, sizeof *statbuf); - statbuf->st_dev = r->dev; - statbuf->st_ino = r->ino; - statbuf->st_mode = r->mode; - statbuf->st_nlink = r->nlink; - statbuf->st_uid = r->uid; - statbuf->st_gid = r->gid; - statbuf->st_rdev = r->rdev; - statbuf->st_size = r->size; - statbuf->st_blksize = r->blksize; - statbuf->st_blocks = r->blocks; - statbuf->st_atime = r->atime; - statbuf->st_mtime = r->mtime; - statbuf->st_ctime = r->ctime; + statbuf->st_dev = r->st_dev; + statbuf->st_ino = r->st_ino; + statbuf->st_mode = r->st_mode; + statbuf->st_nlink = r->st_nlink; + statbuf->st_uid = r->st_uid; + statbuf->st_gid = r->st_gid; + statbuf->st_rdev = r->st_rdev; + statbuf->st_size = r->st_size; + statbuf->st_blksize = r->st_blksize; + statbuf->st_blocks = r->st_blocks; + statbuf->st_atime = r->st_atime_sec; +#ifdef HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC + statbuf->st_atim.tv_nsec = r->st_atime_nsec; +#endif + statbuf->st_mtime = r->st_mtime_sec; +#ifdef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC + statbuf->st_mtim.tv_nsec = r->st_mtime_nsec; +#endif + statbuf->st_ctime = r->st_ctime_sec; +#ifdef HAVE_STRUCT_STAT_ST_CTIM_TV_NSEC + statbuf->st_ctim.tv_nsec = r->st_ctime_nsec; +#endif return 0; } @@ -1133,7 +1151,7 @@ guestfs__umount_local (guestfs_h *g, * Note on attribute caching: FUSE can cache filesystem attributes for * short periods of time (configurable via -o attr_timeout). It * doesn't cache xattrs, and in any case FUSE caching doesn't solve - * the problem that we have to make a series of guestfs_lstat and + * the problem that we have to make a series of guestfs_lstatns and * guestfs_lgetxattr calls when we first list a directory (thus, many * round trips). * diff --git a/v2v/convert_linux.ml b/v2v/convert_linux.ml index fb254b17a..d932bcc11 100644 --- a/v2v/convert_linux.ml +++ b/v2v/convert_linux.ml @@ -43,7 +43,7 @@ type kernel_info = { ki_version : string; (* version-release *) ki_arch : string; (* Kernel architecture. *) ki_vmlinuz : string; (* The path of the vmlinuz file. *) - ki_vmlinuz_stat : G.stat; (* stat(2) of vmlinuz *) + ki_vmlinuz_stat : G.statns; (* stat(2) of vmlinuz *) ki_initrd : string option; (* Path of initramfs, if found. *) ki_modpath : string; (* The module path. *) ki_modules : string list; (* The list of module names. *) @@ -165,7 +165,7 @@ let rec convert ~verbose ~keep_serial_console (g : G.guestfs) inspect source = if not (g#is_dir ~followsymlinks:true modpath) then raise Not_found; let vmlinuz_stat = - try g#stat vmlinuz with G.Error _ -> raise Not_found in + try g#statns vmlinuz with G.Error _ -> raise Not_found in (* Get/construct the version. XXX Read this from kernel file. *) let version = @@ -357,11 +357,11 @@ let rec convert ~verbose ~keep_serial_console (g : G.guestfs) inspect source = filter_map ( fun vmlinuz -> try - let statbuf = g#stat vmlinuz in + let statbuf = g#statns vmlinuz in let kernel = List.find ( fun { ki_vmlinuz_stat = s } -> - statbuf.G.dev = s.G.dev && statbuf.G.ino = s.G.ino + statbuf.G.st_dev = s.G.st_dev && statbuf.G.st_ino = s.G.st_ino ) installed_kernels in Some kernel with Not_found -> None