Files
libguestfs/fuse/test-fuse.c
Richard W.M. Jones 129e4938ba Use 'error' function consistently throughout.
Wherever we had code which did:

  if (something_bad) {
    perror (...);
    exit (EXIT_FAILURE);
  }

replace this with use of the error(3) function:

  if (something_bad)
    error (EXIT_FAILURE, errno, ...);

The error(3) function is supplied by glibc, or by gnulib on platforms
which don't have it, and is much more flexible than perror(3).  Since
we already use error(3), there seems to be no downside to mandating it
everywhere.

Note there is one nasty catch with error(3): error (EXIT_SUCCESS, ...)
does *not* exit!  This is also the reason why error(3) cannot be
marked as __attribute__((noreturn)).

Because the examples can't use gnulib, I did not change them.

To search for multiline patterns of the above form, pcregrep -M turns
out to be very useful:

  pcregrep --buffer-size 10M -M '\bperror\b.*\n.*\bexit\b' `git ls-files`
2016-04-04 13:14:26 +01:00

709 lines
17 KiB
C

/* Test FUSE.
* Copyright (C) 2009-2016 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 <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <error.h>
#ifdef HAVE_ACL
#include <sys/acl.h>
#include <acl/libacl.h>
#endif
#ifdef HAVE_ATTR_XATTR_H
#include <attr/xattr.h>
#else
#ifdef HAVE_SYS_XATTR_H
#include <sys/xattr.h>
#endif
#endif
#include <guestfs.h>
#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)
error (77, errno, "access: /dev/fuse");
g = guestfs_create ();
if (g == NULL)
error (EXIT_FAILURE, errno, "guestfs_create");
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)
error (EXIT_FAILURE, errno, "fork");
if (pid == 0) { /* Child. */
/* Move into the mountpoint for the tests. */
if (chdir (mountpoint) == -1) {
perror (mountpoint);
_exit (EXIT_FAILURE);
}
res = test_fuse ();
printf ("test_fuse() returned %d\n", res);
fflush (stdout);
/* 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, "%s %s", FUSER, mountpoint);
printf ("%s\n", cmd);
fflush (stdout);
ignore_value (system (cmd));
/* Unmount it. */
snprintf (cmd, sizeof cmd, "guestunmount %s", mountpoint);
printf ("%s\n", cmd);
fflush (stdout);
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)
error (EXIT_FAILURE, errno, "waitpid");
if (rmdir (mountpoint) == -1)
error (EXIT_FAILURE, errno, "rmdir: %s", mountpoint);
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 = 0;
struct stat statbuf;
char buf[128];
ssize_t r;
unsigned u, u1;
int fd;
struct timeval tv[2];
struct timespec ts[2];
#ifdef HAVE_ACL
acl_t acl;
char *acl_text;
#endif
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");
fclose (fp);
return -1;
}
if (getline (&line, &len, fp) == -1) {
perror ("getline: hello.txt");
fclose (fp);
return -1;
}
if (STRNEQ (line, "hello")) {
fprintf (stderr, "'hello.txt' does not contain expected content\n");
fclose (fp);
return -1;
}
fclose (fp);
fp = fopen ("world.txt", "r");
if (fp == NULL) {
perror ("open: world.txt");
fclose (fp);
return -1;
}
if (getline (&line, &len, fp) == -1) {
perror ("getline: world.txt");
fclose (fp);
return -1;
}
if (STRNEQ (line, "hello world")) {
fprintf (stderr, "'world.txt' does not contain expected content\n");
fclose (fp);
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");
fclose (fp);
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;
}
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;
}
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");
close (fd);
return -1;
}
if (fstat (fd, &statbuf) == -1) {
perror ("fstat: timestamp");
close (fd);
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);
close (fd);
return -1;
}
close (fd);
STAGE ("checking writes");
fp = fopen ("new.txt", "w");
if (fp == NULL) {
perror ("open: new.txt");
fclose (fp);
return -1;
}
for (u = 0; u < 1000; ++u) {
if (fprintf (fp, "line %u\n", u) == -1) {
perror ("fprintf: new.txt");
fclose (fp);
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");
fclose (fp);
return -1;
}
if (sscanf (line, "line %u", &u1) != 1 || u != u1) {
fprintf (stderr, "unexpected content: %s\n", line);
fclose (fp);
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_any_text (acl, NULL, '\n', TEXT_SOME_EFFECTIVE | TEXT_NUMERIC_IDS);
if (acl_text == NULL) {
perror ("acl_to_any_text: acl");
return -1;
}
if (STRNEQ (acl_text, "user::rwx\nuser:500:r--\ngroup::rwx\nmask::rwx\nother::r-x")) {
fprintf (stderr, "unexpected acl: %s\n", acl_text);
return -1;
}
acl_free (acl_text);
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;
}