From aca076e2e2b3e03f4cf5b09cf7766925f97e4b68 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Sun, 21 Sep 2014 21:40:13 +0100 Subject: [PATCH] fuse: Rewrite test-fuse.sh in C. This gives us finer control over how system calls are done, and also potentially lets us test more. Currently two tests are disabled: - utimens because of https://bugzilla.redhat.com/show_bug.cgi?id=1144766 - utimes because our stat call does not return the nanosecond fields --- .gitignore | 1 + fuse/Makefile.am | 22 +- fuse/test-fuse.c | 698 ++++++++++++++++++++++++++++++++++++++++++++++ fuse/test-fuse.sh | 264 ------------------ po/POTFILES | 1 + 5 files changed, 720 insertions(+), 266 deletions(-) create mode 100644 fuse/test-fuse.c delete mode 100755 fuse/test-fuse.sh diff --git a/.gitignore b/.gitignore index 86158eb68..269d735e1 100644 --- a/.gitignore +++ b/.gitignore @@ -184,6 +184,7 @@ Makefile.in /fuse/guestunmount.1 /fuse/stamp-guestmount.pod /fuse/stamp-guestunmount.pod +/fuse/test-fuse /fuse/test-guestmount-fd /fuse/test-guestunmount-fd /generator/.depend diff --git a/fuse/Makefile.am b/fuse/Makefile.am index 028633879..8bd1acf95 100644 --- a/fuse/Makefile.am +++ b/fuse/Makefile.am @@ -131,7 +131,7 @@ TESTS = \ if ENABLE_APPLIANCE TESTS += \ - test-fuse.sh \ + test-fuse \ test-fuse-umount-race.sh \ test-guestmount-fd endif ENABLE_APPLIANCE @@ -140,7 +140,25 @@ TESTS_ENVIRONMENT = \ top_builddir=.. \ $(top_builddir)/run --test -check_PROGRAMS = test-guestmount-fd test-guestunmount-fd +check_PROGRAMS = test-fuse test-guestmount-fd test-guestunmount-fd + +test_fuse_SOURCES = \ + test-fuse.c + +test_fuse_CPPFLAGS = \ + -I$(top_srcdir)/src -I$(top_builddir)/src \ + -I$(srcdir)/../gnulib/lib -I../gnulib/lib + +test_fuse_CFLAGS = \ + $(WARN_CFLAGS) $(WERROR_CFLAGS) + +test_fuse_LDADD = \ + $(top_builddir)/src/libutils.la \ + $(top_builddir)/src/libguestfs.la \ + $(LIBXML2_LIBS) \ + $(LIBVIRT_LIBS) \ + $(ACL_LIBS) \ + ../gnulib/lib/libgnu.la test_guestmount_fd_SOURCES = \ test-guestmount-fd.c diff --git a/fuse/test-fuse.c b/fuse/test-fuse.c new file mode 100644 index 000000000..dda6fdeb3 --- /dev/null +++ b/fuse/test-fuse.c @@ -0,0 +1,698 @@ +/* Test FUSE. + * Copyright (C) 2009-2014 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* This used to be a shell script test, but using C gives us finer + * control over exactly which system calls are being used, as well as + * allowing us to avoid one launch of the appliance during the test. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_ACL +#include +#endif + +#ifdef HAVE_ATTR_XATTR_H +#include +#else +#ifdef HAVE_SYS_XATTR_H +#include +#endif +#endif + +#include +#include "guestfs-internal-frontend.h" + +#include "ignore-value.h" + +static guestfs_h *g; + +#define SIZE INT64_C(1024*1024*1024) + +/* NB: Must be a path that does not need quoting. */ +static char mountpoint[] = "/tmp/testfuseXXXXXX"; + +static int acl_available; +static int linuxxattrs_available; + +static void create_initial_filesystem (void); +static int test_fuse (void); + +int +main (int argc, char *argv[]) +{ + const char *s; + const char *acl_group[] = { "acl", NULL }; + const char *linuxxattrs_group[] = { "linuxxattrs", NULL }; + int debug_calls, r, res; + pid_t pid; + struct sigaction sa; + char cmd[128]; + + /* Allow the test to be skipped. Note I'm using the old shell + * script name here. + */ + s = getenv ("SKIP_TEST_FUSE_SH"); + if (s && STRNEQ (s, "")) { + printf ("%s: test skipped because environment variable is set\n", + argv[0]); + exit (77); + } + + if (access ("/dev/fuse", W_OK) == -1) { + perror ("/dev/fuse"); + exit (77); + } + + g = guestfs_create (); + if (g == NULL) { + perror ("guestfs_create"); + exit (EXIT_FAILURE); + } + + if (guestfs_add_drive_scratch (g, SIZE, -1) == -1) + exit (EXIT_FAILURE); + + if (guestfs_launch (g) == -1) + exit (EXIT_FAILURE); + + /* Test features. */ + acl_available = guestfs_feature_available (g, (char **) acl_group); + if (acl_available == -1) exit (EXIT_FAILURE); + + linuxxattrs_available = + guestfs_feature_available (g, (char **) linuxxattrs_group); + if (linuxxattrs_available == -1) exit (EXIT_FAILURE); + + create_initial_filesystem (); + + /* Make a mountpoint. */ + if (mkdtemp (mountpoint) == NULL) + exit (EXIT_FAILURE); + + /* Mount the filesystem on the host using FUSE. */ + debug_calls = guestfs_get_trace (g); + if (guestfs_mount_local (g, mountpoint, + GUESTFS_MOUNT_LOCAL_DEBUGCALLS, debug_calls, + -1) == -1) + exit (EXIT_FAILURE); + + /* Fork to run the next part of the test. */ + pid = fork (); + if (pid == -1) { + perror ("fork"); + exit (EXIT_FAILURE); + } + + if (pid == 0) { /* Child. */ + /* Move into the mountpoint for the tests. */ + if (chdir (mountpoint) == -1) { + perror (mountpoint); + _exit (EXIT_FAILURE); + } + + res = test_fuse (); + + /* Move out of the mountpoint (otherwise our cwd will prevent the + * mountpoint from being unmounted below). + */ + ignore_value (chdir ("/")); + + /* Who's using the mountpoint? Should be no one. */ + snprintf (cmd, sizeof cmd, "/sbin/fuser %s", mountpoint); + ignore_value (system (cmd)); + + snprintf (cmd, sizeof cmd, "guestunmount %s", mountpoint); + r = system (cmd); + if (!WIFEXITED (r) || WEXITSTATUS (r) != EXIT_SUCCESS) + fprintf (stderr, "%s: warning: guestunmount command failed\n", argv[0]); + + _exit (res == 0 ? EXIT_SUCCESS : EXIT_FAILURE); + } + + /* Parent. */ + + /* Ignore signals in the parent while running the child. */ + memset (&sa, 0, sizeof sa); + sa.sa_handler = SIG_IGN; + sigaction (SIGINT, &sa, NULL); + sigaction (SIGTERM, &sa, NULL); + + if (guestfs_mount_local_run (g) == -1) + exit (EXIT_FAILURE); + + /* Clean up and exit. */ + if (waitpid (pid, &r, 0) == -1) { + perror ("waitpid"); + exit (EXIT_FAILURE); + } + + if (rmdir (mountpoint) == -1) { + perror (mountpoint); + exit (EXIT_FAILURE); + } + + if (guestfs_shutdown (g) == -1) + exit (EXIT_FAILURE); + + guestfs_close (g); + + /* Did the child process fail? */ + exit (!WIFEXITED (r) || WEXITSTATUS (r) != 0 ? EXIT_FAILURE : EXIT_SUCCESS); +} + +/* Create a filesystem with some initial content. */ +static void +create_initial_filesystem (void) +{ + if (guestfs_part_disk (g, "/dev/sda", "mbr") == -1) + exit (EXIT_FAILURE); + + /* Use ext4 because it supports modern features. Use >= 256 byte + * inodes because these support nanosecond timestamps. + */ + if (guestfs_mkfs_opts (g, "ext4", "/dev/sda1", + GUESTFS_MKFS_OPTS_INODE, 256, + -1) == -1) + exit (EXIT_FAILURE); + + if (guestfs_mount_options (g, "acl,user_xattr", "/dev/sda1", "/") == -1) + exit (EXIT_FAILURE); + + if (guestfs_write (g, "/hello.txt", "hello", 5) == -1) + exit (EXIT_FAILURE); + + if (guestfs_write (g, "/world.txt", "hello world", 11) == -1) + exit (EXIT_FAILURE); + + if (guestfs_touch (g, "/empty") == -1) + exit (EXIT_FAILURE); + + if (linuxxattrs_available) { + if (guestfs_touch (g, "/user_xattr") == -1) + exit (EXIT_FAILURE); + + if (guestfs_setxattr (g, "user.test", "hello123", 8, "/user_xattr") == -1) + exit (EXIT_FAILURE); + } + + if (acl_available) { + if (guestfs_touch (g, "/acl") == -1) + exit (EXIT_FAILURE); + + if (guestfs_acl_set_file (g, "/acl", "access", + "u::rwx,u:500:r,g::rwx,m::rwx,o::r-x") == -1) + exit (EXIT_FAILURE); + } +} + +/* Run FUSE tests. Mountpoint is current directory. */ +static int +test_fuse (void) +{ + int stage = 0; +#define STAGE(fs,...) \ + printf ("%02d: " fs "\n", ++stage, ##__VA_ARGS__); \ + fflush (stdout) + FILE *fp; + char *line = NULL; + size_t len; + struct stat statbuf; + char buf[128]; + ssize_t r; + unsigned u, u1; +#if 0 + int fd; + struct timeval tv[2]; + struct timespec ts[2]; +#endif + acl_t acl; + char *acl_text; + + STAGE ("checking initial files exist"); + + if (access ("empty", F_OK) == -1) { + perror ("access: empty"); + return -1; + } + if (access ("hello.txt", F_OK) == -1) { + perror ("access: hello.txt"); + return -1; + } + if (access ("world.txt", F_OK) == -1) { + perror ("access: world.txt"); + return -1; + } + + STAGE ("checking initial files contain expected content"); + + fp = fopen ("hello.txt", "r"); + if (fp == NULL) { + perror ("open: hello.txt"); + return -1; + } + if (getline (&line, &len, fp) == -1) { + perror ("getline: hello.txt"); + return -1; + } + if (STRNEQ (line, "hello")) { + fprintf (stderr, "'hello.txt' does not contain expected content\n"); + return -1; + } + fclose (fp); + + fp = fopen ("world.txt", "r"); + if (fp == NULL) { + perror ("open: world.txt"); + return -1; + } + if (getline (&line, &len, fp) == -1) { + perror ("getline: world.txt"); + return -1; + } + if (STRNEQ (line, "hello world")) { + fprintf (stderr, "'world.txt' does not contain expected content\n"); + return -1; + } + fclose (fp); + + STAGE ("checking file modes and sizes of initial content"); + + if (stat ("empty", &statbuf) == -1) { + perror ("stat: empty"); + return -1; + } + if ((statbuf.st_mode & 0777) != 0644) { + fprintf (stderr, "'empty' has invalid mode (%o)\n", statbuf.st_mode); + return -1; + } + if (statbuf.st_size != 0) { + fprintf (stderr, "'empty' has invalid size (%d)\n", (int) statbuf.st_size); + return -1; + } + + if (stat ("hello.txt", &statbuf) == -1) { + perror ("stat: hello.txt"); + return -1; + } + if ((statbuf.st_mode & 0777) != 0644) { + fprintf (stderr, "'hello.txt' has invalid mode (%o)\n", statbuf.st_mode); + return -1; + } + if (statbuf.st_size != 5) { + fprintf (stderr, "'hello.txt' has invalid size (%d)\n", + (int) statbuf.st_size); + return -1; + } + + if (stat ("world.txt", &statbuf) == -1) { + perror ("stat: world.txt"); + return -1; + } + if ((statbuf.st_mode & 0777) != 0644) { + fprintf (stderr, "'world.txt' has invalid mode (%o)\n", statbuf.st_mode); + return -1; + } + if (statbuf.st_size != 11) { + fprintf (stderr, "'world.txt' has invalid size (%d)\n", + (int) statbuf.st_size); + return -1; + } + + STAGE ("checking unlink"); + + fp = fopen ("new", "w"); + if (fp == NULL) { + perror ("open: new"); + return -1; + } + fclose (fp); + + if (unlink ("new") == -1) { + perror ("unlink: new"); + return -1; + } + + STAGE ("checking symbolic link"); + + if (symlink ("hello.txt", "symlink") == -1) { + perror ("symlink: hello.txt, symlink"); + return -1; + } + if (lstat ("symlink", &statbuf) == -1) { + perror ("lstat: symlink"); + return -1; + } + if (!S_ISLNK (statbuf.st_mode)) { + fprintf (stderr, "'symlink' is not a symlink (mode = %o)\n", + statbuf.st_mode); + return -1; + } + + STAGE ("checking readlink"); + + r = readlink ("symlink", buf, sizeof buf); + if (r == -1) { + perror ("readlink: symlink"); + return -1; + } + /* readlink return buffer is not \0-terminated, hence: */ + if (r != 9 || memcmp (buf, "hello.txt", r) != 0) { + fprintf (stderr, "readlink on 'symlink' returned incorrect result\n"); + return -1; + } + + STAGE ("checking hard link"); + + if (stat ("hello.txt", &statbuf) == -1) { + perror ("stat: hello.txt"); + return -1; + } + if (statbuf.st_nlink != 1) { + fprintf (stderr, "nlink of 'hello.txt' was %d (expected 1)\n", + (int) statbuf.st_nlink); + return -1; + } + + if (link ("hello.txt", "link") == -1) { + perror ("link: hello.txt, link"); + return -1; + } + + if (stat ("link", &statbuf) == -1) { + perror ("stat: link"); + return -1; + } + if (statbuf.st_nlink != 2) { + fprintf (stderr, "nlink of 'link' was %d (expected 2)\n", + (int) statbuf.st_nlink); + return -1; + } + + if (stat ("hello.txt", &statbuf) == -1) { + perror ("stat: hello.txt"); + return -1; + } + if (statbuf.st_nlink != 2) { + fprintf (stderr, "nlink of 'hello.txt' was %d (expected 2)\n", + (int) statbuf.st_nlink); + return -1; + } + + if (unlink ("link") == -1) { + perror ("unlink: link"); + return -1; + } + +#if 0 + /* This fails because of caching. The problem is that the linked file + * ("hello.txt") is cached with a link count of 2. unlink("link") + * invalidates the cache for "link", but _not_ for "hello.txt" which + * still has the now-incorrect cached value. However there's not much + * we can do about this since searching for all linked inodes of a file + * is an O(n) operation. + */ + if (stat ("hello.txt", &statbuf) == -1) { + perror ("stat: hello.txt"); + return -1; + } + if (statbuf.st_nlink != 1) { + fprintf (stderr, "nlink of 'hello.txt' was %d (expected 1)\n", + (int) statbuf.st_nlink); + return -1; + } +#endif + + STAGE ("checking mkdir"); + + if (mkdir ("newdir", 0755) == -1) { + perror ("mkdir: newdir"); + return -1; + } + + STAGE ("checking rmdir"); + + if (rmdir ("newdir") == -1) { + perror ("rmdir: newdir"); + return -1; + } + + STAGE ("checking rename"); + + fp = fopen ("old", "w"); + if (fp == NULL) { + perror ("open: old"); + return -1; + } + fclose (fp); + if (rename ("old", "new") == -1) { + perror ("rename: old, new"); + return -1; + } + if (access ("new", F_OK) == -1) { + perror ("access: new"); + return -1; + } + if (access ("old", F_OK) == 0) { + fprintf (stderr, "file 'old' exists after rename\n"); + return -1; + } + if (unlink ("new") == -1) { + perror ("unlink: new"); + return -1; + } + + STAGE ("checking chmod"); + + fp = fopen ("new", "w"); + if (fp == NULL) { + perror ("open: new"); + return -1; + } + fclose (fp); + for (u = 0; u < 0777; u += 0111) { + if (chmod ("new", u) == -1) { + perror ("chmod: new"); + return -1; + } + if (stat ("new", &statbuf) == -1) { + perror ("stat: new"); + return -1; + } + if ((statbuf.st_mode & 0777) != u) { + fprintf (stderr, "unexpected mode: was %o expected %o\n", + statbuf.st_mode, u); + return -1; + } + } + if (unlink ("new") == -1) { + perror ("unlink: new"); + return -1; + } + + STAGE ("checking truncate"); + + fp = fopen ("truncated", "w"); + if (fp == NULL) { + perror ("open: truncated"); + return -1; + } + fclose (fp); + for (u = 10000; u <= 10000; u -= 1000) { + if (truncate ("truncated", u) == -1) { + perror ("truncate"); + return -1; + } + if (stat ("truncated", &statbuf) == -1) { + perror ("stat: truncated"); + return -1; + } + if (statbuf.st_size != u) { + fprintf (stderr, "unexpected size: was %u expected %u\n", + (unsigned) statbuf.st_size, u); + return -1; + } + } + if (unlink ("truncated") == -1) { + perror ("unlink: truncated"); + return -1; + } + +#if 0 + STAGE ("checking utimes"); + + fd = open ("timestamp", O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY|O_CLOEXEC, 0644); + if (fd == -1) { + perror ("open: timestamp"); + return -1; + } + close (fd); + tv[0].tv_sec = 23; /* atime */ + tv[0].tv_usec = 45; + tv[1].tv_sec = 67; /* mtime */ + tv[1].tv_usec = 89; + if (utimes ("timestamp", tv) == -1) { + perror ("utimes: timestamp"); + return -1; + } + if (stat ("timestamp", &statbuf) == -1) { + perror ("fstat: timestamp"); + return -1; + } + if (statbuf.st_atime != 23 || + statbuf.st_atim.tv_nsec != 45000 || + statbuf.st_mtime != 67 || + statbuf.st_mtim.tv_nsec != 89000) { + fprintf (stderr, "utimes did not set time (%d/%d/%d/%d)\n", + (int) statbuf.st_atime, (int) statbuf.st_atim.tv_nsec, + (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 */ + STAGE ("checking utimens"); + + fd = open ("timestamp", O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY|O_CLOEXEC, 0644); + if (fd == -1) { + perror ("open: timestamp"); + return -1; + } + ts[0].tv_sec = 12; /* atime */ + ts[0].tv_nsec = 34; + ts[1].tv_sec = 56; /* mtime */ + ts[1].tv_nsec = 78; + if (futimens (fd, ts) == -1) { + perror ("futimens: timestamp"); + return -1; + } + if (fstat (fd, &statbuf) == -1) { + perror ("fstat: timestamp"); + return -1; + } + if (statbuf.st_atime != 12 || + statbuf.st_atim.tv_nsec != 34 || + statbuf.st_mtime != 56 || + statbuf.st_mtim.tv_nsec != 78) { + fprintf (stderr, "utimens did not set time (%d/%d/%d/%d)\n", + (int) statbuf.st_atime, (int) statbuf.st_atim.tv_nsec, + (int) statbuf.st_mtime, (int) statbuf.st_mtim.tv_nsec); + return -1; + } + close (fd); +#endif + + STAGE ("checking writes"); + + fp = fopen ("new.txt", "w"); + if (fp == NULL) { + perror ("open: new.txt"); + return -1; + } + for (u = 0; u < 1000; ++u) { + if (fprintf (fp, "line %u\n", u) == -1) { + perror ("fprintf: new.txt"); + return -1; + } + } + if (fclose (fp) == -1) { + perror ("fclose: new.txt"); + return -1; + } + + fp = fopen ("new.txt", "r"); + if (fp == NULL) { + perror ("open: new.txt"); + return -1; + } + for (u = 0; u < 1000; ++u) { + if (getline (&line, &len, fp) == -1) { + perror ("getline: new.txt"); + return -1; + } + if (sscanf (line, "line %u", &u1) != 1 || u != u1) { + fprintf (stderr, "unexpected content: %s\n", line); + return -1; + } + } + fclose (fp); + +#ifdef HAVE_ACL + if (acl_available) { + STAGE ("checking POSIX ACL read operation"); + + acl = acl_get_file ("acl", ACL_TYPE_ACCESS); + if (acl == (acl_t) NULL) { + perror ("acl_get_file: acl"); + return -1; + } + acl_text = acl_to_text (acl, NULL); + if (acl_text == NULL) { + perror ("acl_to_text: acl"); + return -1; + } + if (STRNEQ (acl_text, "user::rwx\nuser:500:r--\ngroup::rwx\nmask::rwx\nother::r-x\n")) { + fprintf (stderr, "unexpected acl: %s\n", acl_text); + return -1; + } + acl_free (acl); + } +#endif + +#if HAVE_GETXATTR + if (linuxxattrs_available) { + STAGE ("checking extended attribute (xattr) read operation"); + + r = getxattr ("user_xattr", "user.test", buf, sizeof buf); + if (r == -1) { + perror ("getxattr"); + return -1; + } + if (r != 8 || memcmp (buf, "hello123", r) != 0) { + fprintf (stderr, "user.test xattr on file user_xattr was incorrect\n"); + return -1; + } + } +#endif + + /* XXX: + These ones are not yet tested by the current program: + - statfs/statvfs + + These ones cannot easily be tested by the current program, because + this program doesn't run as root: + - fsync + - chown + - mknod + */ + + free (line); + return 0; +} diff --git a/fuse/test-fuse.sh b/fuse/test-fuse.sh deleted file mode 100755 index 13d1bd3c8..000000000 --- a/fuse/test-fuse.sh +++ /dev/null @@ -1,264 +0,0 @@ -#!/bin/bash - -# libguestfs -# Copyright (C) 2009-2014 Red Hat Inc. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -unset CDPATH -set -e -#set -v - -if [ -n "$SKIP_TEST_FUSE_SH" ]; then - echo "$0: test skipped because environment variable is set." - exit 77 -fi - -if [ ! -w /dev/fuse ]; then - echo "SKIPPING guestmount test, because there is no /dev/fuse." - exit 77 -fi - -if ! setfacl --help >/dev/null 2>&1; then - echo "SKIPPING guestmount test, because setfacl is not installed." - exit 77 -fi - -if [ -z "$top_builddir" ]; then - echo "$0: error: environment variable \$top_builddir must be set" - exit 1 -fi - -nr_stages=$(grep "^stage " $0 | wc -l) - -# Allow top_builddir to be a relative path, but also make it absolute, -# and move to that directory for the initial phase of the script. -top_builddir=$(cd "$top_builddir" > /dev/null; pwd) - -# Paths to the other files. NB: Must be absolute paths. -image="$top_builddir/fuse/test-fuse.img" -mp="$top_builddir/fuse/test-fuse-mp" - -if ! guestfish --help >/dev/null 2>&1 || ! guestmount --help >/dev/null 2>&1 || ! guestunmount --help >/dev/null 2>&1 -then - echo "$0: error: guestfish, guestmount or guestunmount are not available" - exit 1 -fi - -# Ensure the mountpoint directory exists and is not being used. -rm -f "$image" -mkdir -p "$mp" -fusermount -u "$mp" >/dev/null 2>&1 ||: - -# Ensure everything is cleaned up on exit. -mounted= - -function cleanup () -{ - status=$? - set +e - [ $status = 0 ] || echo "*** FAILED ***" - echo "Unmounting filesystem and cleaning up." - - # Move out of the mountpoint (otherwise our cwd will prevent the - # mountpoint from being unmounted). - cd "$top_builddir" - - # Who's using this? Should be no one, but see below. - if [ -x /sbin/fuser ]; then /sbin/fuser "$mp"; fi - - if [ -n "$mounted" ]; then - guestunmount "$mp" - fi - - rm -f "$image" - rm -rf "$mp" - exit $status -} -trap cleanup INT TERM QUIT EXIT - -s=1 -function stage () -{ - echo "test-fuse: $s/$nr_stages:" "$@" "..." - ((s++)) -} - -stage Create filesystem with some initial content -guestfish < /dev/null -EOF - -stage Mounting the filesystem -guestmount \ - -a "$image" -m /dev/sda1:/:acl,user_xattr \ - -o uid="$(id -u)" -o gid="$(id -g)" "$mp" -# To debug guestmount, add this to the end of the preceding command: -# -v -x & sleep 60 -mounted=yes - -stage Changing into mounted directory -cd "$mp" - -stage Checking initial files exist -[ -n "$(echo *)" ] -[ "$(ls empty hello.txt world.txt)" = "empty -hello.txt -world.txt" ] - -stage Checking initial files contain expected content -[ "$(cat hello.txt)" = "hello" ] -[ "$(cat world.txt)" = "hello world" ] -cat empty ;# should print nothing -[ -z "$(cat empty)" ] - -stage Checking file modes of initial content -[ "$(stat -c %a empty)" = "644" ] -[ "$(stat -c %a hello.txt)" = "644" ] -[ "$(stat -c %a world.txt)" = "644" ] - -stage Checking sizes of initial content -[ "$(stat -c %s empty)" -eq 0 ] -[ "$(stat -c %s hello.txt)" -eq 5 ] -[ "$(stat -c %s world.txt)" -eq 11 ] - -stage Checking unlink -touch new -rm -f new ;# force because file is "owned" by root - -stage Checking symbolic link -ln -s hello.txt symlink -[ -L symlink ] - -stage Checking readlink -[ "$(readlink symlink)" = "hello.txt" ] - -stage Checking hard link -[ "$(stat -c %h hello.txt)" -eq 1 ] -ln hello.txt link -[ "$(stat -c %h link)" -eq 2 ] -[ "$(stat -c %h hello.txt)" -eq 2 ] -rm -f link -[ ! -e link ] - -# This fails because of caching. The problem is that the linked file -# ("hello.txt") is cached with a link count of 2. unlink("link") -# invalidates the cache for "link", but _not_ for "hello.txt" which -# still has the now-incorrect cached value. However there's not much -# we can do about this since searching for all linked inodes of a file -# is an O(n) operation. -#[ "$(stat -c %h hello.txt)" -eq 1 ] - -stage Checking mkdir -mkdir newdir -[ -d newdir ] - -stage Checking rmdir -rmdir newdir -[ ! -e newdir ] - -stage Checking rename -touch old -mv old new -[ -f new ] -[ ! -e old ] -rm -f new - -stage Checking chmod -touch new -chmod a+x new -[ -x new ] -chmod a-x new -[ ! -x new ] -chmod a-w new -[ ! -w new ] -chmod a+w new -[ -w new ] -chmod a-r new -[ ! -r new ] -chmod a+r new -[ -r new ] -rm -f new - -stage Checking truncate -# RHEL 5 didn't have the truncate command. -if truncate --help >/dev/null 2>&1; then - truncate -s 10000 truncated - [ "$(stat -c %s truncated)" -eq 10000 ] - truncate -c -s 1000 truncated - [ "$(stat -c %s truncated)" -eq 1000 ] - truncate -c -s 10 truncated - [ "$(stat -c %s truncated)" -eq 10 ] - truncate -c -s 0 truncated - [ "$(stat -c %s truncated)" -eq 0 ] - rm -f truncated -fi - -stage Checking utimens and timestamps -for ts in 12345 1234567 987654321; do - # NB: It's not possible to set the ctime with touch. - touch -a -d @$ts timestamp - [ "$(stat -c %X timestamp)" -eq $ts ] - touch -m -d @$ts timestamp - [ "$(stat -c %Y timestamp)" -eq $ts ] - touch -d @$ts timestamp - [ "$(stat -c %X timestamp)" -eq $ts ] - [ "$(stat -c %Y timestamp)" -eq $ts ] -done - -stage Checking writes -cp hello.txt copy.txt -echo >> copy.txt -echo world >> copy.txt -echo bigger >> copy.txt -echo biggest >> copy.txt -[ "$(cat copy.txt)" = "hello -world -bigger -biggest" ] - -stage 'Checking extended attribute (xattr) read operation' -if getfattr --help > /dev/null 2>&1 ; then - [ "$(getfattr -d user_xattr | grep -v ^#)" = 'user.test="hello123"' ] -fi - -stage Checking POSIX ACL read operation -if getfacl --help > /dev/null 2>&1 ; then - [ "$(getfacl -n acl | grep -v ^#)" = "user::rw- -user:500:r-- -group::r-- -mask::r-- -other::r--" ] -fi - -# These ones are not yet tested by the current script: -#stage XXX statfs/statvfs - -# These ones cannot easily be tested by the current script, eg because -# this script doesn't run as root: -#stage XXX fsync -#stage XXX chown -#stage XXX mknod diff --git a/po/POTFILES b/po/POTFILES index 59233c96e..add74b635 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -166,6 +166,7 @@ fish/windows.c format/format.c fuse/guestmount.c fuse/guestunmount.c +fuse/test-fuse.c fuse/test-guestmount-fd.c fuse/test-guestunmount-fd.c gobject/src/optargs-add_domain.c