To my knowledge, it is not possible to implement AT_EMPTY_PATH in userspace in a race-free manner (even with the /proc/self/fd kludge), so I'd like to add the for missing system calls. coreutils, libselinux and policycoreutils need these interfaces to address some corner cases. I will wire up ppc64 and s390x in a second round. I can't test other architectures. I'll submit glibc and strace patches as well, once this is in. Anything else that needs to be taken care of? This is the test program I used: #include <errno.h> #include <fcntl.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> #include <sys/xattr.h> #include <sys/xattr.h> #include <unistd.h> #include <asm/unistd.h> #ifndef O_PATH #define O_PATH 010000000 #endif #ifndef AT_EMPTY_PATH #define AT_EMPTY_PATH 0x1000 #endif #define EXPECT_FAIL(code, ret) expect_fail((code), (ret), __FILE__, __LINE__) static void expect_fail(int code, long ret, const char *file, int line) { if (ret != -1) { fprintf(stderr, "%s:%d: unexpected success\n", file, line); abort(); } if (errno != code) { fprintf(stderr, "%s:%d: unexpected errno=%d (%s)\n", file, line, errno, strerror(errno)); abort(); } } #define EXPECT_CHECK(expr) expect_check(!!(expr), __FILE__, __LINE__) static void expect_check(int expr, const char *file, int line) { if (!expr) { fprintf(stderr, "%s:%d: unexpected failure errno=%d (%s)\n", file, line, errno, strerror(errno)); abort(); } } #define EXPECT_SUCCESS(ret) expect_success((ret), __FILE__, __LINE__) static long expect_success(long ret, const char *file, int line) { if (ret == -1) { fprintf(stderr, "%s:%d: unexpected failure ret=%ld errno=%d (%s)\n", file, line, ret, errno, strerror(errno)); abort(); } return ret; } #define VERIFY(expr) verify(!!(expr), __FILE__, __LINE__) static void verify(int check, const char *file, int line) { if (!check) { fprintf(stderr, "%s:%d: failure\n", file, line); abort(); } } static char tempdir[128]; static int dirfd; static void setup(void) { // Use /var/tmp because it is less likely to be backed by tmpfs. snprintf(tempdir, sizeof(tempdir), "/var/tmp/fsetxattr-test.XXXXXX"); EXPECT_CHECK(mkdtemp(tempdir) != NULL); dirfd = EXPECT_SUCCESS(open(tempdir, O_DIRECTORY | O_RDONLY | O_CLOEXEC)); int filefd; filefd = EXPECT_SUCCESS(openat(dirfd, "file1", O_WRONLY | O_CREAT | O_CLOEXEC, 0666)); EXPECT_SUCCESS(close(filefd)); filefd = EXPECT_SUCCESS(openat(dirfd, "file2", O_WRONLY | O_CREAT | O_CLOEXEC, 0666)); EXPECT_SUCCESS(symlinkat("file1", dirfd, "symlink1")); EXPECT_SUCCESS(symlinkat("does-not-exist", dirfd, "symlink3")); } static void cleanup(void) { EXPECT_SUCCESS(unlinkat(dirfd, "file1", 0)); EXPECT_SUCCESS(unlinkat(dirfd, "file2", 0)); EXPECT_SUCCESS(unlinkat(dirfd, "symlink1", 0)); EXPECT_SUCCESS(unlinkat(dirfd, "symlink3", 0)); EXPECT_SUCCESS(close(dirfd)); EXPECT_SUCCESS(rmdir(tempdir)); } static void check_attr(const char *path, const char *attrname, const char *attrvalue, int flags) { char buf[128]; memset(buf, 'X', sizeof(buf)); ssize_t ret = syscall(__NR_fgetxattrat, dirfd, path, attrname, buf, sizeof(buf), flags); if (attrvalue == NULL) { EXPECT_FAIL(ENODATA, ret); } else { EXPECT_SUCCESS(ret); VERIFY((size_t)ret == strlen(attrvalue)); VERIFY(memcmp(buf, attrvalue, ret) == 0); for (unsigned i = ret; i < sizeof(buf); ++i) { VERIFY(buf[i] == 'X'); } } } struct expected_list { size_t length; const char *data; }; #define EXPECTED_LIST(string) \ (&(struct expected_list){.length = sizeof(string), .data = (string)}) static void check_attr_list(const char *path, const struct expected_list *exp, int flags) { char buf[4096]; memset(buf, 'X', sizeof(buf)); ssize_t ret = syscall(__NR_flistxattrat, dirfd, path, buf, sizeof(buf), flags); EXPECT_SUCCESS(ret); VERIFY((size_t)ret <= sizeof(buf)); unsigned buf_count = 0; unsigned exp_count = 0; for (unsigned bufpos = 0; bufpos < ret; bufpos += strlen(buf + bufpos) + 1) { bool found = false; ++buf_count; exp_count = 0; for (unsigned exppos = 0; exppos < exp->length; exppos += strlen(exp->data + exppos) + 1) { ++exp_count; if (strcmp(exp->data + exppos, buf + bufpos) == 0) { found = true; } } VERIFY(found); } VERIFY(buf_count == exp_count); } static void check_without_at(void) { int curdirfd = open(".", O_DIRECTORY | O_CLOEXEC); EXPECT_SUCCESS(curdirfd); EXPECT_SUCCESS(fchdir(dirfd)); // The EPERM failure might be specific to XFS. EXPECT_FAIL(EPERM, lsetxattr("symlink1", "user.test-attr", "symlink1-attr", strlen("symlink1-attr"), 0)); EXPECT_SUCCESS(fchdir(curdirfd)); EXPECT_SUCCESS(close(curdirfd)); } static void check_at_directory(void) { const char *attrname = "user.attr-name"; check_attr("file1", attrname, NULL, 0); check_attr("file1", attrname, NULL, AT_SYMLINK_NOFOLLOW); check_attr("file1", attrname, NULL, AT_EMPTY_PATH); check_attr("file1", attrname, NULL, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH); check_attr("symlink1", attrname, NULL, 0); check_attr("symlink1", attrname, NULL, AT_SYMLINK_NOFOLLOW); check_attr("symlink1", attrname, NULL, AT_EMPTY_PATH); check_attr("symlink1", attrname, NULL, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH); check_attr_list("file1", EXPECTED_LIST(""), 0); check_attr_list("file1", EXPECTED_LIST(""), AT_SYMLINK_NOFOLLOW); check_attr_list("file1", EXPECTED_LIST(""), AT_EMPTY_PATH); check_attr_list("file1", EXPECTED_LIST(""), AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH); check_attr_list("symlink1", EXPECTED_LIST(""), 0); check_attr_list("symlink1", EXPECTED_LIST(""), AT_SYMLINK_NOFOLLOW); check_attr_list("symlink1", EXPECTED_LIST(""), AT_EMPTY_PATH); check_attr_list("symlink1", EXPECTED_LIST(""), AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH); EXPECT_SUCCESS(syscall(__NR_fsetxattrat, dirfd, "file1", attrname, "file1-attr", strlen("file1-attr"), 0)); check_attr("file1", attrname, "file1-attr", 0); check_attr("file1", attrname, "file1-attr", AT_SYMLINK_NOFOLLOW); check_attr("file1", attrname, "file1-attr", AT_EMPTY_PATH); check_attr("file1", attrname, "file1-attr", AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH); check_attr("symlink1", attrname, "file1-attr", 0); check_attr("symlink1", attrname, NULL, AT_SYMLINK_NOFOLLOW); check_attr("symlink1", attrname, "file1-attr", AT_EMPTY_PATH); check_attr("symlink1", attrname, NULL, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH); // Listing attributes. check_attr_list("file1", EXPECTED_LIST("user.attr-name"), 0); check_attr_list("file1", EXPECTED_LIST("user.attr-name"), AT_SYMLINK_NOFOLLOW); check_attr_list("file1", EXPECTED_LIST("user.attr-name"), AT_EMPTY_PATH); check_attr_list("file1", EXPECTED_LIST("user.attr-name"), AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH); check_attr_list("symlink1", EXPECTED_LIST("user.attr-name"), 0); check_attr_list("symlink1", EXPECTED_LIST("user.attr-name"), AT_SYMLINK_NOFOLLOW); check_attr_list("symlink1", EXPECTED_LIST("user.attr-name"), AT_EMPTY_PATH); check_attr_list("symlink1", EXPECTED_LIST("user.attr-name"), AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH); // Listing multiple attributes. EXPECT_SUCCESS(syscall(__NR_fsetxattrat, dirfd, "file2", "user.k1", "v1", 2, 0)); EXPECT_SUCCESS(syscall(__NR_fsetxattrat, dirfd, "file2", "user.k2", "v2", 2, 0)); check_attr_list("file2", EXPECTED_LIST("user.k1\000user.k2"), 0); // Attributes on symlinks are not supported. EXPECT_FAIL(EPERM, syscall(__NR_fsetxattrat, dirfd, "symlink1", attrname, "symlink1-attr", strlen("symlink1-attr"), AT_SYMLINK_NOFOLLOW)); check_attr("file1", attrname, "file1-attr", 0); check_attr("symlink1", attrname, "file1-attr", 0); check_attr("symlink1", attrname, NULL, AT_SYMLINK_NOFOLLOW); // Attribute removal. EXPECT_SUCCESS(syscall(__NR_fremovexattrat, dirfd, "file1", attrname, 0)); check_attr("file1", attrname, NULL, 0); EXPECT_SUCCESS(syscall(__NR_fsetxattrat, dirfd, "file1", attrname, "file1-attr", strlen("file1-attr"), 0)); EXPECT_SUCCESS(syscall(__NR_fremovexattrat, dirfd, "file1", attrname, AT_SYMLINK_NOFOLLOW)); check_attr("file1", attrname, NULL, 0); EXPECT_SUCCESS(syscall(__NR_fsetxattrat, dirfd, "file1", attrname, "file1-attr", strlen("file1-attr"), 0)); EXPECT_SUCCESS(syscall(__NR_fremovexattrat, dirfd, "file1", attrname, AT_EMPTY_PATH)); check_attr("file1", attrname, NULL, 0); EXPECT_SUCCESS(syscall(__NR_fsetxattrat, dirfd, "file1", attrname, "file1-attr", strlen("file1-attr"), 0)); EXPECT_SUCCESS(syscall(__NR_fremovexattrat, dirfd, "file1", attrname, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)); check_attr("file1", attrname, NULL, 0); EXPECT_SUCCESS(syscall(__NR_fsetxattrat, dirfd, "file1", attrname, "file1-attr", strlen("file1-attr"), 0)); // Replacement and creation. EXPECT_SUCCESS(syscall(__NR_fsetxattrat, dirfd, "file1", attrname, "file1-attr", strlen("file1-attr"), 0)); check_attr("file1", attrname, "file1-attr", 0); EXPECT_SUCCESS(syscall(__NR_fsetxattrat, dirfd, "file1", attrname, "file1-attr-1", strlen("file1-attr-1"), XATTR_REPLACE)); check_attr("file1", attrname, "file1-attr-1", 0); EXPECT_FAIL(EEXIST, syscall(__NR_fsetxattrat, dirfd, "file1", attrname, "file1-attr", strlen("file1-attr-2"), XATTR_CREATE)); check_attr("file1", attrname, "file1-attr-1", 0); EXPECT_SUCCESS(syscall(__NR_fremovexattrat, dirfd, "file1", attrname, 0)); EXPECT_FAIL(ENODATA, syscall(__NR_fsetxattrat, dirfd, "file1", attrname, "file1-attr-1", strlen("file1-attr-1"), XATTR_REPLACE)); check_attr("file1", attrname, NULL, 0); // Replacement and creation through symlinks. EXPECT_SUCCESS(syscall(__NR_fsetxattrat, dirfd, "symlink1", attrname, "file1-attr", strlen("file1-attr"), 0)); check_attr("file1", attrname, "file1-attr", 0); EXPECT_SUCCESS(syscall(__NR_fsetxattrat, dirfd, "symlink1", attrname, "file1-attr-1", strlen("file1-attr-1"), XATTR_REPLACE)); check_attr("file1", attrname, "file1-attr-1", 0); EXPECT_FAIL(EEXIST, syscall(__NR_fsetxattrat, dirfd, "symlink1", attrname, "file1-attr", strlen("file1-attr-2"), XATTR_CREATE)); check_attr("file1", attrname, "file1-attr-1", 0); EXPECT_SUCCESS(syscall(__NR_fsetxattrat, dirfd, "symlink1", attrname, "file1-attr-2", strlen("file1-attr-2"), XATTR_REPLACE)); check_attr("file1", attrname, "file1-attr-2", 0); EXPECT_SUCCESS(syscall(__NR_fremovexattrat, dirfd, "symlink1", attrname, 0)); EXPECT_FAIL(ENODATA, syscall(__NR_fsetxattrat, dirfd, "symlink1", attrname, "file1-attr-1", strlen("file1-attr-1"), XATTR_REPLACE)); check_attr("file1", attrname, NULL, 0); // Attribute removal through symlink. EXPECT_SUCCESS(syscall(__NR_fsetxattrat, dirfd, "file1", attrname, "file1-attr", strlen("file1-attr"), 0)); EXPECT_FAIL(EPERM, syscall(__NR_fremovexattrat, dirfd, "symlink1", attrname, AT_SYMLINK_NOFOLLOW)); check_attr("file1", attrname, "file1-attr", 0); EXPECT_FAIL(EPERM, syscall(__NR_fremovexattrat, dirfd, "symlink1", attrname, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)); check_attr("file1", attrname, "file1-attr", 0); EXPECT_SUCCESS(syscall(__NR_fremovexattrat, dirfd, "symlink1", attrname, 0)); check_attr("file1", attrname, NULL, 0); EXPECT_SUCCESS(syscall(__NR_fsetxattrat, dirfd, "file1", attrname, "file1-attr", strlen("file1-attr"), 0)); EXPECT_SUCCESS(syscall(__NR_fremovexattrat, dirfd, "symlink1", attrname, AT_EMPTY_PATH)); check_attr("file1", attrname, NULL, 0); } static void check_at_path_file(const char *name) { const char *attrname = "user.attr-name"; char buf[128]; int fd = openat(dirfd, name, O_PATH | O_CLOEXEC); EXPECT_SUCCESS(fd); // Classic f* functions must not work with O_PATH. EXPECT_FAIL(EBADF, fsetxattr(fd, attrname, "file1-attr", strlen("file1-attr"), 0)); EXPECT_FAIL(EBADF, fsetxattr(fd, attrname, "file1-attr", strlen("file1-attr"), XATTR_REPLACE)); EXPECT_FAIL(EBADF, fsetxattr(fd, attrname, "file1-attr", strlen("file1-attr"), XATTR_CREATE)); EXPECT_FAIL(EBADF, fgetxattr(fd, attrname, buf, sizeof(buf))); EXPECT_FAIL(EBADF, flistxattr(fd, buf, sizeof(buf))); EXPECT_FAIL(EBADF, fremovexattr(fd, attrname)); // The f*at functions must not work with O_PATH if AT_EMPTY_PATH is // not specified. EXPECT_FAIL(ENOENT, syscall(__NR_fsetxattrat, fd, "", attrname, "file1-attr", strlen("file1-attr"), 0)); EXPECT_FAIL(ENOENT, syscall(__NR_fsetxattrat, fd, "", attrname, "file1-attr", strlen("file1-attr"), XATTR_REPLACE)); EXPECT_FAIL(ENOENT, syscall(__NR_fsetxattrat, fd, "", attrname, "file1-attr", strlen("file1-attr"), XATTR_CREATE)); EXPECT_FAIL(ENOENT, syscall(__NR_fgetxattrat, fd, "", attrname, buf, sizeof(buf), 0)); EXPECT_FAIL(ENOENT, syscall(__NR_flistxattrat, fd, "", buf, sizeof(buf), 0)); EXPECT_FAIL(ENOENT, syscall(__NR_fremovexattrat, fd, "", attrname, 0)); // Once more, with ".". EXPECT_FAIL(ENOTDIR, syscall(__NR_fsetxattrat, fd, ".", attrname, "file1-attr", strlen("file1-attr"), 0)); EXPECT_FAIL(ENOTDIR, syscall(__NR_fsetxattrat, fd, ".", attrname, "file1-attr", strlen("file1-attr"), XATTR_REPLACE)); EXPECT_FAIL(ENOTDIR, syscall(__NR_fsetxattrat, fd, ".", attrname, "file1-attr", strlen("file1-attr"), XATTR_CREATE)); EXPECT_FAIL(ENOTDIR, syscall(__NR_fgetxattrat, fd, ".", attrname, buf, sizeof(buf), 0)); EXPECT_FAIL(ENOTDIR, syscall(__NR_flistxattrat, fd, ".", buf, sizeof(buf), 0)); EXPECT_FAIL(ENOTDIR, syscall(__NR_fremovexattrat, fd, ".", attrname, 0)); // Now tests that are supposed to succeed (at least in part). check_attr("file1", attrname, NULL, 0); EXPECT_SUCCESS(syscall(__NR_fsetxattrat, fd, "", attrname, "file1-attr", strlen("file1-attr"), AT_EMPTY_PATH)); check_attr("file1", attrname, "file1-attr", 0); EXPECT_SUCCESS(syscall(__NR_fsetxattrat, fd, "", attrname, "file1-attr-2", strlen("file1-attr-2"), AT_EMPTY_PATH | XATTR_REPLACE)); check_attr("file1", attrname, "file1-attr-2", 0); EXPECT_FAIL(EEXIST, syscall(__NR_fsetxattrat, fd, "", attrname, "file1-attr-3", strlen("file1-attr-3"), AT_EMPTY_PATH | XATTR_CREATE)); check_attr("file1", attrname, "file1-attr-2", 0); EXPECT_SUCCESS(syscall(__NR_fremovexattrat, fd, "", attrname, AT_EMPTY_PATH)); EXPECT_FAIL(ENODATA, syscall(__NR_fsetxattrat, fd, "", attrname, "file1-attr-3", strlen("file1-attr-3"), AT_EMPTY_PATH | XATTR_REPLACE)); check_attr("file1", attrname, NULL, 0); // Compare two different ways for listing attributes. { char buf1[128]; char buf2[128]; ssize_t ret1; ssize_t ret2; EXPECT_SUCCESS(syscall(__NR_fsetxattrat, fd, "", "user.k1", "v1", 2, AT_EMPTY_PATH)); EXPECT_SUCCESS(syscall(__NR_fsetxattrat, fd, "", "user.k2", "v2", 2, AT_EMPTY_PATH)); check_attr_list("file1", EXPECTED_LIST("user.k1\000user.k2"), 0); ret1 = syscall(__NR_flistxattrat, dirfd, "file1", buf1, sizeof(buf1), 0); EXPECT_SUCCESS(ret1); VERIFY((size_t)ret1 < sizeof(buf1)); ret2 = syscall(__NR_flistxattrat, fd, "", buf2, sizeof(buf2), AT_EMPTY_PATH); EXPECT_SUCCESS(ret2); VERIFY(ret1 == ret2); VERIFY(memcmp(buf1, buf2, ret1) == 0); EXPECT_SUCCESS(syscall(__NR_fremovexattrat, fd, "", "user.k1", AT_EMPTY_PATH)); EXPECT_SUCCESS(syscall(__NR_fremovexattrat, fd, "", "user.k2", AT_EMPTY_PATH)); } EXPECT_SUCCESS(close(fd)); } static void check_at_path_symlink(void) { const char *attrname = "user.attr-name"; char buf[128]; ssize_t ret; int fd = openat(dirfd, "symlink1", O_PATH | O_CLOEXEC | O_NOFOLLOW); EXPECT_SUCCESS(fd); { struct stat st; EXPECT_SUCCESS(fstatat(fd, "", &st, AT_EMPTY_PATH)); VERIFY(S_ISLNK(st.st_mode)); } // The f*at functions must not work with O_PATH if AT_EMPTY_PATH is // not specified. EXPECT_FAIL(ENOENT, syscall(__NR_fsetxattrat, fd, "", attrname, "file1-attr", strlen("file1-attr"), AT_SYMLINK_NOFOLLOW)); EXPECT_FAIL(ENOENT, syscall(__NR_fsetxattrat, fd, "", attrname, "file1-attr", strlen("file1-attr"), AT_SYMLINK_NOFOLLOW | XATTR_REPLACE)); EXPECT_FAIL(ENOENT, syscall(__NR_fsetxattrat, fd, "", attrname, "file1-attr", strlen("file1-attr"), AT_SYMLINK_NOFOLLOW | XATTR_CREATE)); EXPECT_FAIL(ENOENT, syscall(__NR_fgetxattrat, fd, "", attrname, buf, sizeof(buf), AT_SYMLINK_NOFOLLOW)); EXPECT_FAIL(ENOENT, syscall(__NR_flistxattrat, fd, "", buf, sizeof(buf), AT_SYMLINK_NOFOLLOW)); EXPECT_FAIL(ENOENT, syscall(__NR_fremovexattrat, fd, "", attrname, AT_SYMLINK_NOFOLLOW)); // Once more, with ".". EXPECT_FAIL(ENOTDIR, syscall(__NR_fsetxattrat, fd, ".", attrname, "file1-attr", strlen("file1-attr"), AT_SYMLINK_NOFOLLOW)); EXPECT_FAIL(ENOTDIR, syscall(__NR_fsetxattrat, fd, ".", attrname, "file1-attr", strlen("file1-attr"), AT_SYMLINK_NOFOLLOW | XATTR_REPLACE)); EXPECT_FAIL(ENOTDIR, syscall(__NR_fsetxattrat, fd, ".", attrname, "file1-attr", strlen("file1-attr"), AT_SYMLINK_NOFOLLOW | XATTR_CREATE)); EXPECT_FAIL(ENOTDIR, syscall(__NR_fgetxattrat, fd, ".", attrname, buf, sizeof(buf), AT_SYMLINK_NOFOLLOW)); EXPECT_FAIL(ENOTDIR, syscall(__NR_flistxattrat, fd, ".", buf, sizeof(buf), AT_SYMLINK_NOFOLLOW)); EXPECT_FAIL(ENOTDIR, syscall(__NR_fremovexattrat, fd, ".", attrname, AT_SYMLINK_NOFOLLOW)); // Operations directly on the symlink. First setup. EXPECT_SUCCESS(syscall(__NR_fsetxattrat, dirfd, "file1", attrname, "preserve", strlen("preserve"), AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)); check_attr("file1", attrname, "preserve", 0); // EXPECT_FAIL(EPERM, syscall(__NR_fsetxattrat, fd, "", attrname, "file1-attr", strlen("file1-attr"), AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)); EXPECT_FAIL(EPERM, syscall(__NR_fremovexattrat, fd, "", attrname, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)); memset(buf, 'X', sizeof(buf)); ret = syscall(__NR_flistxattrat, fd, "", buf, sizeof(buf), AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH); EXPECT_SUCCESS(ret); VERIFY(ret == 0); for (unsigned i = 0; i < sizeof(buf); ++i) { VERIFY(buf[i] == 'X'); } // check_attr("file1", attrname, "preserve", 0); EXPECT_SUCCESS(close(fd)); } int main(void) { setup(); check_without_at(); check_at_directory(); cleanup(); setup(); check_at_path_file("file1"); cleanup(); setup(); check_at_path_file("symlink1"); cleanup(); setup(); check_at_path_symlink(); cleanup(); return 0; } Florian Weimer (3): vfs: Introduce XATTR_SET_MASK vfs: Implement fsetxattrat, fgetxattrat, flistxattrat, fremovexattrat x86: wire fsetxattrat, fgetxattrat, flistxattrat, fremovexattrat syscalls arch/x86/syscalls/syscall_32.tbl | 4 ++ arch/x86/syscalls/syscall_64.tbl | 4 ++ fs/xattr.c | 126 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 133 insertions(+), 1 deletion(-) -- 1.8.3.1 -- To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html