From 55a7427b7679e25134cd43488a9f74cb542416ea Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Sat, 31 Oct 2009 13:41:18 +0000 Subject: [PATCH] New API calls: lstatlist, lxattrlist, readlinklist. These three functions are very specifically designed for FUSE support, so we can list directories efficiently. Instead of making lots of lstat, lgetxattr and readlink calls, we can make just three calls per directory to grab all the attributes (which we then cache briefly). --- daemon/link.c | 51 ++++++++++++++ daemon/stat.c | 68 ++++++++++++++++++ daemon/xattr.c | 205 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/MAX_PROC_NR | 2 +- src/generator.ml | 73 ++++++++++++++++++++ 5 files changed, 398 insertions(+), 1 deletion(-) diff --git a/daemon/link.c b/daemon/link.c index a77c2e8..5ea0d39 100644 --- a/daemon/link.c +++ b/daemon/link.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -51,6 +52,56 @@ do_readlink (const char *path) return ret; /* caller frees */ } +char ** +do_readlinklist (const char *path, char *const *names) +{ + int fd_cwd; + size_t i; + ssize_t r; + char link[PATH_MAX]; + const char *str; + char **ret = NULL; + int size = 0, alloc = 0; + + CHROOT_IN; + fd_cwd = open (path, O_RDONLY | O_DIRECTORY); + CHROOT_OUT; + + if (fd_cwd == -1) { + reply_with_perror ("readlinklist: %s", path); + return NULL; + } + + for (i = 0; names[i] != NULL; ++i) { + r = readlinkat (fd_cwd, names[i], link, sizeof link); + if (r >= PATH_MAX) { + reply_with_perror ("readlinkat: returned link is too long"); + free_strings (ret); + close (fd_cwd); + return NULL; + } + /* Because of the way this function is intended to be used, + * we actually expect to see errors here, and they are not fatal. + */ + if (r >= 0) { + link[r] = '\0'; + str = link; + } else + str = ""; + if (add_string (&ret, &size, &alloc, str) == -1) { + close (fd_cwd); + return NULL; + } + } + + close (fd_cwd); + + if (add_string (&ret, &size, &alloc, NULL) == -1) + return NULL; + + return ret; +} + static int _link (const char *flag, int symbolic, const char *target, const char *linkname) { diff --git a/daemon/stat.c b/daemon/stat.c index 4ff2711..2441c9f 100644 --- a/daemon/stat.c +++ b/daemon/stat.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include "../src/guestfs_protocol.h" @@ -108,6 +109,73 @@ do_lstat (const char *path) return ret; } +guestfs_int_stat_list * +do_lstatlist (const char *path, char *const *names) +{ + int path_fd; + guestfs_int_stat_list *ret; + size_t i, nr_names; + + nr_names = count_strings (names); + + ret = malloc (sizeof *ret); + if (!ret) { + 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) { + reply_with_perror ("malloc"); + free (ret); + return NULL; + } + + CHROOT_IN; + path_fd = open (path, O_RDONLY | O_DIRECTORY); + CHROOT_OUT; + + if (path_fd == -1) { + reply_with_perror ("lstatlist: %s", path); + free (ret->guestfs_int_stat_list_val); + free (ret); + return NULL; + } + + for (i = 0; names[i] != NULL; ++i) { + int r; + struct stat statbuf; + + 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; + ret->guestfs_int_stat_list_val[i].blksize = statbuf.st_blksize; + ret->guestfs_int_stat_list_val[i].blocks = statbuf.st_blocks; + 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; + } + } + + if (close (path_fd) == -1) { + reply_with_perror ("close: %s", path); + free (ret->guestfs_int_stat_list_val); + free (ret); + return NULL; + } + + return ret; +} + guestfs_int_statvfs * do_statvfs (const char *path) { diff --git a/daemon/xattr.c b/daemon/xattr.c index 1531070..d16939f 100644 --- a/daemon/xattr.c +++ b/daemon/xattr.c @@ -243,6 +243,204 @@ _removexattr (const char *xattr, const char *path, return 0; } +guestfs_int_xattr_list * +do_lxattrlist (const char *path, char *const *names) +{ +#if defined(HAVE_LLISTXATTR) && defined(HAVE_LGETXATTR) + /* XXX This would be easier if the kernel had lgetxattrat. In the + * meantime we use this buffer to store the whole path name. + */ + char pathname[PATH_MAX]; + size_t path_len = strlen (path); + guestfs_int_xattr_list *ret = NULL; + int i, j; + size_t k, m, nr_attrs; + ssize_t len, vlen; + char *buf = NULL; + + if (path_len >= PATH_MAX) { + reply_with_perror ("lxattrlist: path longer than PATH_MAX"); + goto error; + } + + strcpy (pathname, path); + + ret = malloc (sizeof (*ret)); + if (ret == NULL) { + reply_with_perror ("malloc"); + goto error; + } + + ret->guestfs_int_xattr_list_len = 0; + ret->guestfs_int_xattr_list_val = NULL; + + for (k = 0; names[k] != NULL; ++k) { + /* Be careful in here about which errors cause the whole call + * to abort, and which errors allow us to continue processing + * the call, recording a special "error attribute" in the + * outgoing struct list. + */ + if (path_len + strlen (names[k]) + 2 > PATH_MAX) { + reply_with_perror ("lxattrlist: path and name longer than PATH_MAX"); + goto error; + } + pathname[path_len] = '/'; + strcpy (&pathname[path_len+1], names[k]); + + /* Reserve space for the special attribute. */ + void *newptr = + realloc (ret->guestfs_int_xattr_list_val, + (ret->guestfs_int_xattr_list_len+1)*sizeof (guestfs_int_xattr)); + if (newptr == NULL) { + reply_with_perror ("realloc"); + goto error; + } + ret->guestfs_int_xattr_list_val = newptr; + ret->guestfs_int_xattr_list_len++; + + guestfs_int_xattr *entry = + &ret->guestfs_int_xattr_list_val[ret->guestfs_int_xattr_list_len-1]; + entry->attrname = NULL; + entry->attrval.attrval_len = 0; + entry->attrval.attrval_val = NULL; + + entry->attrname = strdup (""); + if (entry->attrname == NULL) { + reply_with_perror ("strdup"); + goto error; + } + + CHROOT_IN; + len = llistxattr (pathname, NULL, 0); + CHROOT_OUT; + if (len == -1) + continue; /* not fatal */ + + buf = malloc (len); + if (buf == NULL) { + reply_with_perror ("malloc"); + goto error; + } + + CHROOT_IN; + len = llistxattr (pathname, buf, len); + CHROOT_OUT; + if (len == -1) + continue; /* not fatal */ + + /* What we get from the kernel is a string "foo\0bar\0baz" of length + * len. First count the strings. + */ + nr_attrs = 0; + for (i = 0; i < len; i += strlen (&buf[i]) + 1) + nr_attrs++; + + newptr = + realloc (ret->guestfs_int_xattr_list_val, + (ret->guestfs_int_xattr_list_len+nr_attrs) * + sizeof (guestfs_int_xattr)); + if (newptr == NULL) { + reply_with_perror ("realloc"); + goto error; + } + ret->guestfs_int_xattr_list_val = newptr; + ret->guestfs_int_xattr_list_len += nr_attrs; + + /* entry[0] is the special attribute, + * entry[1..nr_attrs] are the attributes. + */ + entry = &ret->guestfs_int_xattr_list_val[ret->guestfs_int_xattr_list_len-nr_attrs-1]; + for (m = 1; m <= nr_attrs; ++m) { + entry[m].attrname = NULL; + entry[m].attrval.attrval_len = 0; + entry[m].attrval.attrval_val = NULL; + } + + for (i = 0, j = 0; i < len; i += strlen (&buf[i]) + 1, ++j) { + CHROOT_IN; + vlen = lgetxattr (pathname, &buf[i], NULL, 0); + CHROOT_OUT; + if (vlen == -1) { + reply_with_perror ("getxattr"); + goto error; + } + + entry[j+1].attrname = strdup (&buf[i]); + entry[j+1].attrval.attrval_val = malloc (vlen); + entry[j+1].attrval.attrval_len = vlen; + + if (entry[j+1].attrname == NULL || + entry[j+1].attrval.attrval_val == NULL) { + reply_with_perror ("malloc"); + goto error; + } + + CHROOT_IN; + vlen = lgetxattr (pathname, &buf[i], + entry[j+1].attrval.attrval_val, vlen); + CHROOT_OUT; + if (vlen == -1) { + reply_with_perror ("getxattr"); + goto error; + } + } + + free (buf); buf = NULL; + + char num[16]; + snprintf (num, sizeof num, "%zu", nr_attrs); + entry[0].attrval.attrval_len = strlen (num) + 1; + entry[0].attrval.attrval_val = strdup (num); + + if (entry[0].attrval.attrval_val == NULL) { + reply_with_perror ("strdup"); + goto error; + } + } + + /* If verbose, debug what we're about to send back. */ + if (verbose) { + fprintf (stderr, "lxattrlist: returning: [\n"); + for (k = 0; k < ret->guestfs_int_xattr_list_len; ++k) { + const guestfs_int_xattr *entry = &ret->guestfs_int_xattr_list_val[k]; + if (strcmp (entry[0].attrname, "") != 0) { + fprintf (stderr, "ERROR: expecting empty attrname at k = %zu\n", k); + break; + } + fprintf (stderr, " %zu: special attrval = %s\n", + k, entry[0].attrval.attrval_val); + for (i = 1; k+i < ret->guestfs_int_xattr_list_len; ++i) { + if (strcmp (entry[i].attrname, "") == 0) + break; + fprintf (stderr, " name %s, value length %d\n", + entry[i].attrname, entry[i].attrval.attrval_len); + } + k += i-1; + } + fprintf (stderr, "]\n"); + } + + return ret; + + error: + free (buf); + if (ret) { + if (ret->guestfs_int_xattr_list_val) { + for (k = 0; k < ret->guestfs_int_xattr_list_len; ++k) { + free (ret->guestfs_int_xattr_list_val[k].attrname); + free (ret->guestfs_int_xattr_list_val[k].attrval.attrval_val); + } + free (ret->guestfs_int_xattr_list_val); + } + free (ret); + } + return NULL; +#else + reply_with_error ("lxattrlist: no support for llistxattr and lgetxattr"); + return NULL; +#endif +} + #else /* no xattr.h */ guestfs_int_xattr_list * @@ -287,4 +485,11 @@ do_lremovexattr (const char *xattr, const char *path) return -1; } +guestfs_int_xattr_list * +do_lxattrlist (const char *path, char *const *names) +{ + reply_with_error ("lxattrlist: no support for xattrs"); + return NULL; +} + #endif /* no xattr.h */ diff --git a/src/MAX_PROC_NR b/src/MAX_PROC_NR index bb2ee19..b35cfaf 100644 --- a/src/MAX_PROC_NR +++ b/src/MAX_PROC_NR @@ -1 +1 @@ -203 +206 diff --git a/src/generator.ml b/src/generator.ml index b8add4c..001d021 100755 --- a/src/generator.ml +++ b/src/generator.ml @@ -3783,6 +3783,79 @@ 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)."); + ("lstatlist", (RStructList ("statbufs", "stat"), [Pathname "path"; StringList "names"]), 204, [], + [], (* XXX *) + "lstat on multiple files", + "\ +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."); + + ("lxattrlist", (RStructList ("xattrs", "xattr"), [Pathname "path"; StringList "names"]), 205, [], + [], (* XXX *) + "lgetxattr on multiple files", + "\ +This call allows you to get the extended attributes +of multiple files, where all files are in the directory C. +C is the list of files from this directory. + +On return you get a flat list of xattr structs which must be +interpreted sequentially. The first xattr struct always has a zero-length +C. C in this struct is zero-length +to indicate there was an error doing C for this +file, I is a C string which is a decimal number +(the number of following attributes for this file, which could +be C<\"0\">). Then after the first xattr struct are the +zero or more attributes for the first named file. +This repeats for the second and subsequent files. + +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 standard stats. 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."); + + ("readlinklist", (RStringList "links", [Pathname "path"; StringList "names"]), 206, [], + [], (* XXX *) + "readlink on multiple files", + "\ +This call allows you to do a 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 strings, with a one-to-one +correspondence to the C list. Each string is the +value of the symbol link. + +If the C operation fails on any name, then +the corresponding result string is the empty string C<\"\">. +However the whole operation is completed even if there +were C errors, and so you can call this +function with names where you don't know if they are +symbolic links already (albeit slightly less efficient). + +This call is intended for programs that want to efficiently +list a directory contents without making many round-trips. +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."); + ] let all_functions = non_daemon_functions @ daemon_functions -- 1.8.3.1