mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-21 22:53:37 +00:00
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
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
698
fuse/test-fuse.c
Normal file
698
fuse/test-fuse.c
Normal file
@@ -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 <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>
|
||||
|
||||
#ifdef HAVE_ACL
|
||||
#include <sys/acl.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) {
|
||||
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;
|
||||
}
|
||||
@@ -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 <<EOF
|
||||
sparse "$image" 10M
|
||||
run
|
||||
part-disk /dev/sda mbr
|
||||
mkfs ext2 /dev/sda1
|
||||
mount_options acl,user_xattr /dev/sda1 /
|
||||
write /hello.txt hello
|
||||
write /world.txt "hello world"
|
||||
touch /empty
|
||||
touch /user_xattr
|
||||
setxattr user.test hello123 8 /user_xattr
|
||||
touch /acl
|
||||
# XXX hack until libguestfs gets ACL support
|
||||
debug sh "setfacl -m u:500:r /sysroot/acl" | cat > /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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user