xfstests update - Ethans new inode flags test mainly
authorfsgqa <fsgqa>
Mon, 15 Sep 2003 05:11:31 +0000 (05:11 +0000)
committerfsgqa <fsgqa>
Mon, 15 Sep 2003 05:11:31 +0000 (05:11 +0000)
079 [new file with mode: 0755]
079.out [new file with mode: 0644]
group
src/Makefile
src/t_immutable.c [new file with mode: 0644]

diff --git a/079 b/079
new file mode 100755 (executable)
index 0000000..bb55f0c
--- /dev/null
+++ b/079
@@ -0,0 +1,71 @@
+#! /bin/sh
+# XFS QA Test No. 079
+#
+# Run the t_immutable test program for immutable/append-only files.
+#
+#-----------------------------------------------------------------------
+# Copyright (c) 2000-2003 Silicon Graphics, Inc.  All Rights Reserved.
+# 
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of version 2 of the GNU General Public License as
+# published by the Free Software Foundation.
+# 
+# This program is distributed in the hope that it would be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# 
+# Further, this software is distributed without any warranty that it is
+# free of the rightful claim of any third person regarding infringement
+# or the like.  Any license provided herein, whether implied or
+# otherwise, applies only to this software file.  Patent licenses, if
+# any, provided herein do not apply to combinations of this program with
+# other software, or any other product whatsoever.
+# 
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write the Free Software Foundation, Inc., 59
+# Temple Place - Suite 330, Boston MA 02111-1307, USA.
+# 
+# Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy,
+# Mountain View, CA  94043, or:
+# 
+# http://www.sgi.com 
+# 
+# For further information regarding this notice, see: 
+# 
+# http://oss.sgi.com/projects/GenInfo/SGIGPLNoticeExplan/
+#-----------------------------------------------------------------------
+#
+# creator
+owner=nathans@sgi.com
+
+seq=`basename $0`
+echo "QA output created by $seq"
+
+here=`pwd`
+timmutable=$here/src/t_immutable
+tmp=/tmp/$$
+status=1       # failure is the default!
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+_cleanup()
+{
+    echo "*** cleaning up"
+    $timmutable -r $SCRATCH_MNT/$seq
+    umount $SCRATCH_MNT
+}
+
+# get standard environment, filters and checks
+. ./common.rc
+. ./common.filter
+
+_require_scratch
+[ -x $timmutable ] || _notrun "t_immutable was not built for this platform"
+
+# real QA test starts here
+_scratch_mkfs_xfs 2>&1 >/dev/null || _fail "mkfs failed"
+_scratch_mount || _fail "mount failed"
+
+echo "*** starting up"
+$timmutable -c $SCRATCH_MNT/$seq
+status=$?
+exit
diff --git a/079.out b/079.out
new file mode 100644 (file)
index 0000000..b3daaa6
--- /dev/null
+++ b/079.out
@@ -0,0 +1,7 @@
+QA output created by 079
+*** starting up
+testing immutable...PASS.
+testing append-only...PASS.
+testing immutable as non-root...PASS.
+testing append-only as non-root...PASS.
+*** cleaning up
diff --git a/group b/group
index b6d33f57885533c1725675dfa9cc623d2047cf76..e1e1b2be67e248251e4938e583bafc2496e58157 100644 (file)
--- a/group
+++ b/group
@@ -139,3 +139,4 @@ ioctl               nathans@sgi.com
 076 metadata rw
 077 acl attr auto
 078 growfs auto
+079 acl attr ioctl metadata auto
index daa1f7367add0a9cac27f6ab5cf5e69a0c3b6eba..5bbb6c1e516f716f081855625cb3348be72c5679 100644 (file)
@@ -41,6 +41,9 @@ TARGETS = alloc acl_get bstat devzero dirstress fault feature \
 ifeq ($(ENABLE_DBM), yes)
 TARGETS += dbtest
 endif
+ifeq ($(PKG_PLATFORM),linux)
+TARGETS += t_immutable
+endif
 
 CFILES = $(TARGETS:=.c)
 LDIRT = $(TARGETS)
@@ -65,6 +68,9 @@ nametest: nametest.o $(LIBTEST)
 bstat: bstat.o $(LIBHANDLE)
        $(LINKTEST) $(LIBHANDLE) $(LDLIBS)
 
+t_immutable: t_immutable.o $(LIBHANDLE) $(LIBACL)
+       $(LINKTEST) $(LIBACL) $(LIBHANDLE) $(LDLIBS)
+
 loggen:        loggen.o
        $(LINKTEST) $(LDLIBS)
 
diff --git a/src/t_immutable.c b/src/t_immutable.c
new file mode 100644 (file)
index 0000000..854986e
--- /dev/null
@@ -0,0 +1,2341 @@
+/* compile with: gcc -g -O0 -Wall -I/usr/include/xfs -o t_immutable t_immutable.c -lhandle -lacl -lattr */
+
+/*
+ *  t_immutable.c - hideous test suite for immutable/append-only flags.
+ *
+ *  Copyright (C) 2003 Ethan Benson
+ *
+ *  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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#define TEST_UTIME
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/ioctl.h>
+#include <sys/vfs.h>
+#include <utime.h>
+#include <errno.h>
+#include <grp.h>
+#include <libgen.h>
+#include <sys/acl.h>
+#include <attr/xattr.h>
+#include <xfs/libxfs.h>
+#include <xfs/handle.h>
+#include <xfs/jdm.h>
+#include <ext2fs/ext2_fs.h>
+
+
+#define XFS_XFLAG_IMMUTABLE     0x00000008
+#define XFS_XFLAG_APPEND        0x00000010
+#define EXT2_IMMUTABLE_FL       0x00000010
+#define EXT2_APPEND_FL          0x00000020
+
+extern const char *__progname;
+
+static int fsetflag(const char *path, int fd, int on, int immutable)
+{
+     int e2flags = 0;
+     struct fsxattr attr;
+     struct statfs stfs;
+     int xfsfl;
+     int e2fl;
+
+     if (immutable) {
+         xfsfl = XFS_XFLAG_IMMUTABLE;
+         e2fl = EXT2_IMMUTABLE_FL;
+     } else {
+         xfsfl = XFS_XFLAG_APPEND;
+         e2fl = EXT2_APPEND_FL;
+     }
+
+     if (fstatfs(fd, &stfs) != 0)
+         return 1;
+
+     if (stfs.f_type == XFS_SUPER_MAGIC) {
+         if (xfsctl(path, fd, XFS_IOC_FSGETXATTR, &attr) < 0) {
+              close(fd);
+              return 1;
+         }
+         if (on)
+              attr.fsx_xflags |= xfsfl;
+         else
+              attr.fsx_xflags &= ~xfsfl;
+         if (xfsctl(path, fd, XFS_IOC_FSSETXATTR, &attr) < 0) {
+              close(fd);
+              return 1;
+         }
+     } else if (stfs.f_type == EXT2_SUPER_MAGIC) {
+         if (on)
+              e2flags |= e2fl;
+         else 
+              e2flags &= ~e2fl;
+         if (ioctl(fd, EXT2_IOC_SETFLAGS, &e2flags) < 0) {
+              close(fd);
+              return 1;
+         }
+     } else {
+         errno = EOPNOTSUPP;
+         close(fd);
+         return 1;
+     }
+     close(fd);
+     return 0;
+}
+
+static int add_acl(const char *path, const char *acl_text)
+{
+     acl_t acl;
+     int s_errno;
+     int fd;
+
+     if ((acl = acl_from_text(acl_text)) == NULL)
+         return 1;
+     if ((fd = open(path, O_RDONLY)) == -1)
+         return 1;
+     if (acl_set_fd(fd, acl))
+         if (errno != EOPNOTSUPP)
+              return 1;
+     s_errno = errno;
+     acl_free(acl);
+     close(fd);
+     errno = s_errno;
+     return 0;
+}
+
+static int fadd_acl(int fd, const char *acl_text)
+{
+     acl_t acl;
+     int s_errno;
+
+     if ((acl = acl_from_text(acl_text)) == NULL)
+         perror("acl_from_text");
+     if (acl_set_fd(fd, acl))
+         if (errno != EOPNOTSUPP)
+              return 1;
+     s_errno = errno;
+     acl_free(acl);
+     errno = s_errno;
+     return 0;
+}
+
+static int del_acl(const char *path)
+{
+     int fd;
+     int s_errno;
+     acl_t acl;
+     static const char *acl_text = "u::rw-,g::rw-,o::rw-";
+
+     if ((acl = acl_from_text(acl_text)) == NULL)
+         return 1;
+     if ((fd = open(path, O_RDONLY)) == -1)
+         return 1;
+     if (acl_set_fd(fd, acl))
+         if (errno != EOPNOTSUPP)
+              return 1;
+     s_errno = errno;
+     acl_free(acl);
+     close(fd);
+     errno = s_errno;
+     return 0;
+}
+
+static int test_immutable(const char *dir)
+{
+     int fd;
+     char *buf;
+     char *path;
+     char *linkpath;
+     int fail = 0;
+     struct utimbuf tbuf;
+     struct stat st;
+     struct statfs stfs;
+     static const char *scribble = "scribbled by tester\n";
+     static const char *acl_text = "u::rwx,g::rwx,o::rwx,u:daemon:rwx,m::rwx";
+
+     tbuf.actime = 0;
+     tbuf.modtime = 0;
+
+     if (statfs(dir, &stfs) == -1) {
+         perror("statfs failed");
+         return 1;
+     }
+
+     asprintf(&path, "%s/immutable.f", dir);
+     errno = 0;
+     if ((fd = open(path, O_RDWR)) != -1) {
+         fprintf(stderr, "open(%s, O_RDWR) did not fail\n", path);
+         fail++;
+         close(fd);
+     } else if (errno != EACCES) {
+         fprintf(stderr, "open(%s, O_RDWR) did not set errno == EACCES\n", path);
+         fail++;
+     }
+
+     errno = 0;
+     if ((fd = open(path, O_WRONLY)) != -1) {
+         fprintf(stderr, "open(%s, O_WRONLY) did not fail\n", path);
+         fail++;
+         close(fd);
+     } else if (errno != EACCES) {
+          fprintf(stderr, "open(%s, O_WRONLY) did not set errno == EACCES\n", path);
+          fail++;
+     }
+
+     errno = 0;
+     if ((fd = open(path, O_RDWR|O_TRUNC)) != -1) {
+          fprintf(stderr, "open(%s, O_RDWR|O_TRUNC) did not fail\n", path);
+          fail++;
+          close(fd);
+     } else if (errno != EACCES) {
+          fprintf(stderr, "open(%s, O_RDWR|O_TRUNC) did not set errno == EACCES\n", path);
+          fail++;
+     }
+
+     errno = 0;
+     if ((fd = open(path, O_WRONLY|O_TRUNC)) != -1) {
+          fprintf(stderr, "open(%s, O_WRONLY|O_TRUNC) did not fail\n", path);
+          fail++;
+          close(fd);
+     } else if (errno != EACCES) {
+          fprintf(stderr, "open(%s, O_WRONLY|O_TRUNC did not set errno == EACCES\n", path);
+          fail++;
+     }
+
+     errno = 0;
+     if ((fd = open(path, O_RDWR|O_APPEND)) != -1) {
+          fprintf(stderr, "open(%s, O_RDWR|O_APPEND) did not fail\n", path);
+          fail++;
+          close(fd);
+     } else if (errno != EACCES) {
+          fprintf(stderr, "open(%s, O_RDWR|O_APPEND) did not set errno == EACCES\n", path);
+          fail++;
+     }
+
+     errno = 0;
+     if ((fd = open(path, O_WRONLY|O_APPEND)) != -1) {
+          fprintf(stderr, "open(%s, O_WRONLY|O_APPEND) did not fail\n", path);
+          fail++;
+          close(fd);
+     } else if (errno != EACCES) {
+          fprintf(stderr, "open(%s, O_WRONLY|O_APPEND) did not set errno == EACCES\n", path);
+          fail++;
+     }
+
+     errno = 0;
+     if ((fd = open(path, O_RDWR|O_APPEND|O_TRUNC)) != -1) {
+          fprintf(stderr, "open(%s, O_RDWR|O_APPEND|O_TRUNC) did not fail\n", path);
+          fail++;
+          close(fd);
+     } else if (errno != EACCES) {
+          fprintf(stderr, "open(%s, O_RDWR|O_APPEND|O_TRUNC) did not set errno == EACCES\n", path);
+          fail++;
+     }
+
+     errno = 0;
+     if ((fd = open(path, O_WRONLY|O_APPEND|O_TRUNC)) != -1) {
+          fprintf(stderr, "open(%s, O_WRONLY|O_APPEND|O_TRUNC) did not fail\n", path);
+          fail++;
+          close(fd);
+     } else if (errno != EACCES) {
+          fprintf(stderr, "open(%s, O_WRONLY|O_APPEND|O_TRUNC) did not set errno == EACCES\n", path);
+          fail++;
+     }
+
+     if (stfs.f_type == XFS_SUPER_MAGIC && !getuid()) {
+         jdm_fshandle_t *fshandle;
+         xfs_bstat_t bstat;
+         xfs_fsop_bulkreq_t  bulkreq;
+         xfs_ino_t ino;
+         char *dirpath;
+
+         dirpath = strdup(path); /* dirname obnoxiously modifies its arg */
+         if ((fshandle = jdm_getfshandle(dirname(dirpath))) == NULL) {
+              perror("jdm_getfshandle");
+              return 1;
+         }
+         free(dirpath);
+
+         if (stat(path, &st) != 0) {
+              perror("stat");
+              return 1;
+         }
+
+         ino = st.st_ino;
+
+         bulkreq.lastip = &ino;
+         bulkreq.icount = 1;
+         bulkreq.ubuffer = &bstat;
+         bulkreq.ocount = NULL;
+
+         if ((fd = open(path, O_RDONLY)) == -1) {
+              perror("open");
+              return 1;
+         }
+
+         if (ioctl(fd, XFS_IOC_FSBULKSTAT_SINGLE, &bulkreq) == -1) {
+              perror("ioctl(fd, XFS_IOC_FSBULKSTAT_SINGLE");
+              close(fd);
+              return 1;
+         }
+         close(fd);
+
+         errno = 0;
+         if ((fd = jdm_open(fshandle, &bstat, O_RDWR)) != -1) {
+              fprintf(stderr, "jdm_open(%s, O_RDWR) did not fail\n", path);
+              fail++;
+              close(fd);
+         } else if (errno != EACCES) {
+              perror("jdm_open");
+              fprintf(stderr, "jdm_open(%s, O_RDWR) did not set errno == EACCES\n", path);
+              fail++;
+         }
+
+         errno = 0;
+         if ((fd = jdm_open(fshandle, &bstat, O_WRONLY)) != -1) {
+              fprintf(stderr, "jdm_open(%s, O_WRONLY) did not fail\n", path);
+              fail++;
+              close(fd);
+         } else if (errno != EACCES) {
+              fprintf(stderr, "jdm_open(%s, O_WRONLY) did not set errno == EACCES\n", path);
+              fail++;
+         }
+
+         errno = 0;
+         if ((fd = jdm_open(fshandle, &bstat, O_RDWR|O_TRUNC)) != -1) {
+              fprintf(stderr, "jdm_open(%s, O_RDWR|O_TRUNC) did not fail\n", path);
+              fail++;
+              close(fd);
+         } else if (errno != EACCES) {
+              fprintf(stderr, "jdm_open(%s, O_RDWR|O_TRUNC) did not set errno == EACCES\n", path);
+              fail++;
+         }
+
+         errno = 0;
+         if ((fd = jdm_open(fshandle, &bstat, O_WRONLY|O_TRUNC)) != -1) {
+              fprintf(stderr, "jdm_open(%s, O_WRONLY|O_TRUNC) did not fail\n", path);
+              fail++;
+              close(fd);
+         } else if (errno != EACCES) {
+              fprintf(stderr, "jdm_open(%s, O_WRONLY|O_TRUNC did not set errno == EACCES\n", path);
+              fail++;
+         }
+
+         errno = 0;
+         if ((fd = jdm_open(fshandle, &bstat, O_RDWR|O_APPEND)) != -1) {
+              fprintf(stderr, "jdm_open(%s, O_RDWR|O_APPEND) did not fail\n", path);
+              fail++;
+              close(fd);
+         } else if (errno != EACCES) {
+              fprintf(stderr, "jdm_open(%s, O_RDWR|O_APPEND) did not set errno == EACCES\n", path);
+              fail++;
+         }
+
+         errno = 0;
+         if ((fd = jdm_open(fshandle, &bstat, O_WRONLY|O_APPEND)) != -1) {
+              fprintf(stderr, "jdm_open(%s, O_WRONLY|O_APPEND) did not fail\n", path);
+              fail++;
+              close(fd);
+         } else if (errno != EACCES) {
+              fprintf(stderr, "jdm_open(%s, O_WRONLY|O_APPEND) did not set errno == EACCES\n", path);
+              fail++;
+         }
+
+         errno = 0;
+         if ((fd = jdm_open(fshandle, &bstat, O_RDWR|O_APPEND|O_TRUNC)) != -1) {
+              fprintf(stderr, "jdm_open(%s, O_RDWR|O_APPEND|O_TRUNC) did not fail\n", path);
+              fail++;
+              close(fd);
+         } else if (errno != EACCES) {
+              fprintf(stderr, "jdm_open(%s, O_RDWR|O_APPEND|O_TRUNC) did not set errno == EACCES\n", path);
+              fail++;
+         }
+
+         errno = 0;
+         if ((fd = jdm_open(fshandle, &bstat, O_WRONLY|O_APPEND|O_TRUNC)) != -1) {
+              fprintf(stderr, "jdm_open(%s, O_WRONLY|O_APPEND|O_TRUNC) did not fail\n", path);
+              fail++;
+              close(fd);
+         } else if (errno != EACCES) {
+              fprintf(stderr, "jdm_open(%s, O_WRONLY|O_APPEND|O_TRUNC) did not set errno == EACCES\n", path);
+              fail++;
+         }
+     }
+
+     errno = 0;
+     if (truncate(path, 0) != -1) {
+         fprintf(stderr, "truncate(%s, 0) did not fail\n", path);
+         fail++;
+     } else if (errno != EACCES) {
+         fprintf(stderr, "truncate(%s, 0) did not set errno == EACCES\n", path);
+         fail++;
+     }
+
+#ifdef TEST_UTIME
+     errno = 0;
+     if (utime(path, &tbuf) != -1) {
+         fprintf(stderr, "utime(%s, <epoch>) did not fail\n", path);
+         fail++;
+     } else if (errno != EPERM) {
+         fprintf(stderr, "utime(%s, <epoch>) did not set errno == EPERM\n", path);
+         fail++;
+     }
+
+     errno = 0;
+     if (utime(path, NULL) != -1) {
+          fprintf(stderr, "utime(%s, NULL) did not fail\n", path);
+          fail++;
+     } else if (errno != EACCES) {
+          fprintf(stderr, "utime(%s, NULL) did not set errno == EACCES\n", path);
+          fail++;
+     }
+#endif /* TEST_UTIME */
+
+     asprintf(&linkpath, "%s/immutable.f.hardlink", dir);
+     errno = 0;
+     if (link(path, linkpath) != -1) {
+         fprintf(stderr, "link(%s, %s) did not fail\n", path, linkpath);
+         fail++;
+         unlink(linkpath);
+     } else if (errno != EPERM) {
+         fprintf(stderr, "link(%s, %s) did not set errno == EPERM\n", path, linkpath);
+         fail++;
+     }
+     free(linkpath);
+
+     if (!getuid()) { /* these would fail if not root anyway */
+         errno = 0;
+         if (chmod(path, 7777) != -1) {
+              fprintf(stderr, "chmod(%s, 7777) did not fail\n", path);
+              fail++;
+              chmod(path, 0666);
+         } else if (errno != EPERM) {
+              fprintf(stderr, "chmod(%s, 7777) did not set errno == EPERM\n", path);
+              fail++;
+         }
+
+         errno = 0;
+         if (chown(path, 1, 1) != -1) {
+              fprintf(stderr, "chown(%s, 1, 1) did not fail\n", path);
+              fail++;
+              chown(path, 0, 0);
+         } else if (errno != EPERM) {
+              fprintf(stderr, "chown(%s, 1, 1) did not set errno to EPERM\n", path);
+              fail++;
+         }
+
+         errno = 0;
+         if (del_acl(path) != 1) {
+              if (errno != EOPNOTSUPP) {
+              fprintf(stderr, "del_acl(%s) did not fail\n", path);
+              fail++;
+              }
+         } else if (errno != EPERM) {
+              fprintf(stderr, "del_acl(%s) did not set errno == EPERM\n", path);
+              fail++;
+         }
+         errno = 0;
+         if (add_acl(path, acl_text) != 1) {
+              if (errno != EOPNOTSUPP) {
+              fprintf(stderr, "add_acl(%s) did not fail\n", path);
+              fail++;
+              }
+         } else if (errno != EPERM) {
+              fprintf(stderr, "add_acl(%s) did not set errno == EPERM\n", path);
+               fail++;
+          }
+
+          if (setxattr(path, "trusted.test", "scribble", strlen("scribble"), XATTR_REPLACE) != -1) {
+               fprintf(stderr, "setxattr(%s, trusted.test, scribble, 8, XATTR_REPLACE) did not fail\n", path);
+               fail++;
+          } else if (errno != EPERM && errno != EOPNOTSUPP) {
+               fprintf(stderr,
+                       "setxattr(%s, trusted.test, scribble, 8, XATTR_REPLACE) did not set errno == EPERM\n", path);
+               fail++;
+          }
+          if (setxattr(path, "trusted.scribble", "scribble", strlen("scribble"), XATTR_CREATE) != -1) {
+               fprintf(stderr, "setxattr(%s, trusted.scribble, scribble, 8, XATTR_CREATE) did not fail\n", path);
+               fail++;
+          } else if (errno != EPERM && errno != EOPNOTSUPP) {
+               fprintf(stderr,
+                       "setxattr(%s, trusted.scribble, scribble, 8, XATTR_CREATE) did not set errno == EPERM\n", path);
+               fail++;
+          }
+
+          if (removexattr(path, "trusted.test") != -1) {
+               fprintf(stderr, "removexattr(%s, trusted.test) did not fail\n", path);
+               fail++;
+          } else if (errno != EPERM && errno != EOPNOTSUPP) {
+               fprintf(stderr,
+                       "removexattr(%s, trusted.test) did not set errno == EPERM\n", path);
+               fail++;
+          }
+     }
+
+     if (setxattr(path, "user.test", "scribble", strlen("scribble"), XATTR_REPLACE) != -1) {
+          fprintf(stderr, "setxattr(%s, user.test, scribble, 8, XATTR_REPLACE) did not fail\n", path);
+          fail++;
+     } else if (errno != EPERM && errno != EOPNOTSUPP) {
+          fprintf(stderr, "setxattr(%s, user.test, scribble, 8, XATTR_REPLACE) did not set errno == EPERM\n", path);
+          fail++;
+     }
+     if (setxattr(path, "user.scribble", "scribble", strlen("scribble"), XATTR_CREATE) != -1) {
+          fprintf(stderr, "setxattr(%s, user.scribble, scribble, 8, XATTR_CREATE) did not fail\n", path);
+          fail++;
+     } else if (errno != EPERM && errno != EOPNOTSUPP) {
+          fprintf(stderr, "setxattr(%s, user.scribble, scribble, 8, XATTR_CREATE) did not set errno == EPERM\n", path);
+          fail++;
+     }
+     if (removexattr(path, "user.test") != -1) {
+         fprintf(stderr, "removexattr(%s, user.test) did not fail\n", path);
+         fail++;
+     } else if (errno != EPERM && errno != EOPNOTSUPP) {
+         fprintf(stderr,
+                 "removexattr(%s, user.test) did not set errno == EPERM\n", path);
+         fail++;
+     }
+
+     asprintf(&linkpath, "%s/immutable.f.newname", dir);
+     errno = 0;
+     if (rename(path, linkpath) != -1) {
+         fprintf(stderr, "rename(%s, %s) did not fail\n", path, linkpath);
+         fail++;
+         rename(linkpath, path);
+     } else if (errno != EPERM) {
+         fprintf(stderr, "rename(%s, %s) did not set errno == EPERM\n", path, linkpath);
+         fail++;
+     }
+     free(linkpath);
+
+     if ((fd = open(path, O_RDONLY)) == -1) {
+         fprintf(stderr, "open(%s, O_RDONLY) failed: %s\n", path, strerror(errno));
+         fail++;
+     } else {
+         if (fstat(fd, &st) == -1) {
+              perror("fstat");
+              fail++;
+         } else if (st.st_size) {
+              if ((buf = malloc(st.st_size)) == NULL)
+                   perror("malloc");
+              else {
+                   if (lseek(fd, 0, SEEK_SET) == -1) {
+                        perror("lseek(fd, 0, SEEK_SET) failed");
+                        fail++;
+                   }
+                   if (read(fd, buf, st.st_size) != st.st_size) {
+                        perror("read failed");
+                        fail++;
+                   }
+                   free(buf);
+              }
+         }
+         close(fd);
+     }
+
+     errno = 0;
+     if (unlink(path) != -1) {
+         fprintf(stderr, "unlink(%s) did not fail\n", path);
+         fail++;
+     } else if (errno != EPERM) {
+         fprintf(stderr, "unlink(%s) did not set errno == EPERM\n", path);
+         fail ++;
+     }
+
+     free(path);
+     asprintf(&path, "%s/immutable.d/file", dir);
+     if ((fd = open(path, O_RDWR)) == -1) {
+         fprintf(stderr, "open(%s, O_RDWR) failed: %s\n", path, strerror(errno));
+         fail++;
+     } else {
+         if (write(fd, scribble, strlen(scribble)) != strlen(scribble)) {
+              fprintf(stderr, "write(%s, %s, %d) failed: %s\n", path, scribble, strlen(scribble), strerror(errno));
+              fail++;
+         }
+         close(fd);
+     }
+     if (!getuid) {
+         if (chmod(path, 0777) == -1) {
+              fprintf(stderr, "chmod(%s, 0777) failed: %s\n", path, strerror(errno));
+              fail++;
+         } else
+              chmod(path, 0666);
+         if (chown(path, 1, 1) == -1) {
+              fprintf(stderr, "chown(%s, 1, 1) failed: %s\n", path, strerror(errno));
+              fail++;
+         } else
+              chown(path, 0, 0);
+     }
+
+     asprintf(&linkpath, "%s/immutable.d/file.link", dir);
+     errno = 0;
+     if (link(path, linkpath) != -1) { 
+         fprintf(stderr, "link(%s, %s) did not fail\n", path, linkpath);
+         fail++;
+         unlink(linkpath);
+     } else if (errno != EACCES) {
+         fprintf(stderr, "link(%s, %s) did not set errno == EACCES\n", path, linkpath);
+         fail++;
+     }
+     if (symlink(path, linkpath) != -1) {
+         fprintf(stderr, "symlink(%s, %s) did not fail\n", path, linkpath);
+         fail++;
+         unlink(linkpath);
+     } else if (errno != EACCES) {
+         fprintf(stderr, "symlink(%s, %s) did not set errno == EACCES\n", path, linkpath);
+          fail++;
+     }
+     free(linkpath);
+     asprintf(&linkpath, "%s/immutable.d/file.newname", dir);
+     if (rename(path, linkpath) != -1) {
+         fprintf(stderr, "rename(%s, %s) did not fail\n", path, linkpath);
+         fail++;
+         rename(linkpath, path);
+     } else if (errno != EACCES) {
+         fprintf(stderr, "rename(%s, %s) did not set errno == EACCES\n", path, linkpath);
+          fail++;
+     }
+     free(linkpath);
+
+     if (unlink(path) != -1) {
+         fprintf(stderr, "unlink(%s) did not fail\n", path);
+         fail++;
+     } else if (errno != EACCES) {
+         fprintf(stderr, "unlink(%s) did not set errno == EACCES\n", path);
+          fail++;
+     }
+
+     free(path);
+     asprintf(&path, "%s/immutable.d/newfile", dir);
+     errno = 0;
+     if ((fd = open(path, O_RDWR|O_CREAT, 0666)) != -1) {
+         fprintf(stderr, "open(%s, O_RDWR|O_CREAT, 0666) did not fail\n", path);
+         fail++;
+         unlink(path);
+     } else if (errno != EACCES) {
+         fprintf(stderr, "open(%s, O_RDWR|O_CREAT, 0666) did not set errno == EACCES\n", path);
+         fail++;
+     }
+     if (!getuid()) {
+         if (stat("/dev/null", &st) != -1) {
+              if (mknod(path, S_IFCHR|S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH, st.st_rdev) != -1) {
+                   fprintf(stderr, "mknod(%s, S_IFCHR|0666, %lld) did not fail\n", path, st.st_rdev);
+                   fail++;
+                   unlink(path);
+              } else if (errno != EACCES) { 
+                   fprintf(stderr, "mknod(%s, S_IFCHR|0666, %lld) did not set errno == EACCESS\n", path, st.st_rdev);
+                   fail++;
+              }
+         }
+     }
+
+     free(path);
+     asprintf(&path, "%s/immutable.d/newdir", dir);
+     errno = 0;
+     if (mkdir(path, 0777) != -1) {
+         fprintf(stderr, "mkdir(%s, 0777) did not fail\n", path);
+         fail++;
+         rmdir(path);
+     } else if (errno != EACCES) {
+         fprintf(stderr, "mkdir(%s, 0777) did not set errno == EACCES\n", path);
+          fail++;
+     }
+     
+     free(path);
+     asprintf(&path, "%s/immutable.d/dir/newfile-%d", dir, getuid());
+     if ((fd = open(path, O_RDWR|O_CREAT, 0666)) == -1) {
+          fprintf(stderr, "open(%s, O_RDWR|O_CREAT, 0666) failed: %s\n", path, strerror(errno));
+          fail++;
+     } else {
+          if (write(fd, scribble, strlen(scribble)) != strlen(scribble)) {
+               fprintf(stderr, "write(%s, %s, %d) failed: %s\n", path, scribble, strlen(scribble), strerror(errno));
+               fail++;
+          }
+          close(fd);
+     }
+     if (!getuid()) {
+         if (chmod(path, 0700) == -1) {
+              fprintf(stderr, "chmod(%s, 0700) failed: %s\n", path, strerror(errno));
+              fail++;
+         } else
+              chmod(path, 0666);
+
+          if (chown(path, 1, 1) == -1) {
+               fprintf(stderr, "chown(%s, 1, 1) failed: %s\n", path, strerror(errno));
+               fail++;
+          } else
+               chown(path, 0, 0);
+     }
+
+     free(path);
+     asprintf(&path, "%s/immutable.d/dir", dir);
+     errno = 0;
+     if (rmdir(path) != -1) {
+         fprintf(stderr, "rmdir(%s) did not fail\n", path);
+         fail++;
+     } else if (errno != EACCES) {
+         fprintf(stderr, "rmdir(%s) did not set errno == EACCES\n", path);
+         fail++;
+     }
+
+     free(path);
+     asprintf(&path, "%s/immutable.d", dir);
+
+#ifdef TEST_UTIME
+     errno = 0;
+     if (utime(path, &tbuf) != -1) {
+         fprintf(stderr, "utime(%s, <epoch>) did not fail\n", path);
+         fail++;
+     } else if (errno != EPERM) {
+         fprintf(stderr, "utime(%s, <epoch>) did not set errno == EPERM\n", path);
+         fail++;
+     }
+
+     errno = 0;
+     if (utime(path, NULL) != -1) {
+          fprintf(stderr, "utime(%s, NULL) did not fail\n", path);
+          fail++;
+     } else if (errno != EACCES) {
+          fprintf(stderr, "utime(%s, NULL) did not set errno == EACCES\n", path);
+          fail++;
+     }
+#endif /* TEST_UTIME */
+
+     if (!getuid()) { /* these would fail if not root anyway */
+         errno = 0;
+         if (chmod(path, 7777) != -1) {
+              fprintf(stderr, "chmod(%s, 7777) did not fail\n", path);
+              fail++;
+              chmod(path, 0666);
+         } else if (errno != EPERM) {
+              fprintf(stderr, "chmod(%s, 7777) did not set errno == EPERM\n", path);
+              fail++;
+         }
+
+         errno = 0;
+         if (chown(path, 1, 1) != -1) {
+              fprintf(stderr, "chown(%s, 1, 1) did not fail\n", path);
+              fail++;
+              chown(path, 0, 0);
+         } else if (errno != EPERM) {
+              fprintf(stderr, "chown(%s, 1, 1) did not set errno to EPERM\n", path);
+              fail++;
+         }
+
+         errno = 0;
+         if (del_acl(path) != 1) {
+              if (errno != EOPNOTSUPP) {
+              fprintf(stderr, "del_acl(%s) did not fail\n", path);
+              fail++;
+              }
+         } else if (errno != EPERM) {
+              fprintf(stderr, "del_acl(%s) did not set errno == EPERM\n", path);
+              fail++;
+         }
+         errno = 0;
+         if (add_acl(path, acl_text) != 1) {
+              if (errno != EOPNOTSUPP) {
+              fprintf(stderr, "add_acl(%s) did not fail\n", path);
+              fail++;
+              }
+         } else if (errno != EPERM) {
+              fprintf(stderr, "add_acl(%s) did not set errno == EPERM\n", path);
+               fail++;
+          }
+
+         if (setxattr(path, "trusted.test", "scribble", strlen("scribble"), XATTR_REPLACE) != -1) {
+              fprintf(stderr, "setxattr(%s, trusted.test, scribble, 8, XATTR_REPLACE) did not fail\n", path);
+              fail++;
+         } else if (errno != EPERM && errno != EOPNOTSUPP) {
+              fprintf(stderr,
+                      "setxattr(%s, trusted.test, scribble, 8, XATTR_REPLACE) did not set errno == EPERM\n", path);
+              fail++;
+         }
+         if (setxattr(path, "trusted.scribble", "scribble", strlen("scribble"), XATTR_CREATE) != -1) {
+              fprintf(stderr, "setxattr(%s, trusted.scribble, scribble, 8, XATTR_CREATE) did not fail\n", path);
+              fail++;
+         } else if (errno != EPERM && errno != EOPNOTSUPP) {
+              fprintf(stderr,
+                      "setxattr(%s, trusted.scribble, scribble, 8, XATTR_CREATE) did not set errno == EPERM\n", path);
+              fail++;
+         }
+          if (removexattr(path, "trusted.test") != -1) {
+               fprintf(stderr, "removexattr(%s, trusted.test) did not fail\n", path);
+               fail++;
+          } else if (errno != EPERM && errno != EOPNOTSUPP) {
+               fprintf(stderr,
+                       "removexattr(%s, trusted.test) did not set errno == EPERM\n", path);
+               fail++;
+          }
+     }
+
+     if (setxattr(path, "user.test", "scribble", strlen("scribble"), XATTR_REPLACE) != -1) {
+         fprintf(stderr, "setxattr(%s, user.test, scribble, 8, XATTR_REPLACE) did not fail\n", path);
+         fail++;
+     } else if (errno != EPERM && errno != EOPNOTSUPP) {
+         fprintf(stderr, "setxattr(%s, user.test, scribble, 8, XATTR_REPLACE) did not set errno == EPERM\n", path);
+         fail++;
+     }
+     if (setxattr(path, "user.scribble", "scribble", strlen("scribble"), XATTR_CREATE) != -1) {
+          fprintf(stderr, "setxattr(%s, user.scribble, scribble, 8, XATTR_CREATE) did not fail\n", path);
+          fail++;
+     } else if (errno != EPERM && errno != EOPNOTSUPP) {
+          fprintf(stderr, "setxattr(%s, user.scribble, scribble, 8, XATTR_CREATE) did not set errno == EPERM\n", path);
+          fail++;
+     }
+     if (removexattr(path, "user.test") != -1) {
+          fprintf(stderr, "removexattr(%s, user.test) did not fail\n", path);
+          fail++;
+     } else if (errno != EPERM && errno != EOPNOTSUPP) {
+          fprintf(stderr,
+                  "removexattr(%s, user.test) did not set errno == EPERM\n", path);
+          fail++;
+     }
+
+     free(path);
+     asprintf(&path, "%s/empty-immutable.d", dir);
+     errno = 0;
+     if (rmdir(path) != -1) {
+          fprintf(stderr, "rmdir(%s) did not fail\n", path);
+          fail++;
+     } else if (errno != EPERM) {
+          fprintf(stderr, "rmdir(%s) did not set errno == EPERM\n", path);
+          fail++;
+     }     
+
+     free(path);
+     return fail;
+}
+
+static int test_append(const char *dir)
+{
+     int fd;
+     char *buf;
+     char *orig = NULL;
+     char *path;
+     char *linkpath;
+     off_t origsize = 0;
+     int fail = 0;
+     struct utimbuf tbuf;
+     struct stat st;
+     struct statfs stfs;
+     static const char *acl_text = "u::rwx,g::rwx,o::rwx,u:daemon:rwx,m::rwx";
+     static const char *scribble = "scribbled by tester\n";
+     static const char *scribble2 = "scribbled by tester\nscribbled by tester\n";
+     static const char *scribble4 = "scribbled by tester\nscribbled by tester\n"
+         "scribbled by tester\nscribbled by tester\n";
+
+     tbuf.actime = 0;
+     tbuf.modtime = 0;
+
+     if (statfs(dir, &stfs) == -1) {
+          perror("statfs failed");
+          return 1;
+     }
+
+     asprintf(&path, "%s/append-only.f", dir);
+     errno = 0;
+     if ((fd = open(path, O_RDWR)) != -1) {
+         fprintf(stderr, "open(%s, O_RDWR) did not fail\n", path);
+         fail++;
+         close(fd);
+     } else if (errno != EPERM) {
+         fprintf(stderr, "open(%s, O_RDWR) did not set errno == EPERM\n", path);
+         fail++;
+     }
+
+     errno = 0;
+     if ((fd = open(path, O_WRONLY)) != -1) {
+         fprintf(stderr, "open(%s, O_WRONLY) did not fail\n", path);
+         fail++;
+         close(fd);
+     } else if (errno != EPERM) {
+          fprintf(stderr, "open(%s, O_WRONLY) did not set errno == EPERM\n", path);
+          fail++;
+     }
+
+     errno = 0;
+     if ((fd = open(path, O_RDWR|O_TRUNC)) != -1) {
+          fprintf(stderr, "open(%s, O_RDWR|O_TRUNC) did not fail\n", path);
+          fail++;
+          close(fd);
+     } else if (errno != EPERM) {
+          fprintf(stderr, "open(%s, O_RDWR|O_TRUNC) did not set errno == EPERM\n", path);
+          fail++;
+     }
+
+     errno = 0;
+     if ((fd = open(path, O_WRONLY|O_TRUNC)) != -1) {
+          fprintf(stderr, "open(%s, O_WRONLY|O_TRUNC) did not fail\n", path);
+          fail++;
+          close(fd);
+     } else if (errno != EPERM) {
+          fprintf(stderr, "open(%s, O_WRONLY|O_TRUNC did not set errno == EPERM\n", path);
+          fail++;
+     }
+
+     errno = 0;
+     if ((fd = open(path, O_RDWR|O_APPEND|O_TRUNC)) != -1) {
+          fprintf(stderr, "open(%s, O_RDWR|O_APPEND|O_TRUNC) did not fail\n", path);
+          fail++;
+          close(fd);
+     } else if (errno != EPERM) {
+          fprintf(stderr, "open(%s, O_RDWR|O_APPEND|O_TRUNC) did not set errno == EPERM\n", path);
+          fail++;
+     }
+
+     errno = 0;
+     if ((fd = open(path, O_WRONLY|O_APPEND|O_TRUNC)) != -1) {
+          fprintf(stderr, "open(%s, O_WRONLY|O_APPEND|O_TRUNC) did not fail\n", path);
+          fail++;
+          close(fd);
+     } else if (errno != EPERM) {
+          fprintf(stderr, "open(%s, O_WRONLY|O_APPEND|O_TRUNC) did not set errno == EPERM\n", path);
+          fail++;
+     }
+
+     errno = 0;
+     if (truncate(path, 0) != -1) {
+         fprintf(stderr, "truncate(%s, 0) did not fail\n", path);
+         fail++;
+     } else if (errno != EPERM) {
+         fprintf(stderr, "truncate(%s, 0) did not set errno == EPERM\n", path);
+         fail++;
+     }
+
+     if (stfs.f_type == XFS_SUPER_MAGIC && !getuid()) {
+         jdm_fshandle_t *fshandle;
+         xfs_bstat_t bstat;
+         xfs_fsop_bulkreq_t  bulkreq;
+         xfs_ino_t ino;
+         char *dirpath;
+
+         dirpath = strdup(path); /* dirname obnoxiously modifies its arg */
+         if ((fshandle = jdm_getfshandle(dirname(dirpath))) == NULL) {
+              perror("jdm_getfshandle");
+              return 1;
+         }
+         free(dirpath);
+
+         if (stat(path, &st) != 0) {
+              perror("stat");
+              return 1;
+         }
+
+         ino = st.st_ino;
+
+         bulkreq.lastip = &ino;
+         bulkreq.icount = 1;
+         bulkreq.ubuffer = &bstat;
+         bulkreq.ocount = NULL;
+
+         if ((fd = open(path, O_RDONLY)) == -1) {
+              perror("open");
+              return 1;
+         }
+
+         if (ioctl(fd, XFS_IOC_FSBULKSTAT_SINGLE, &bulkreq) == -1) {
+              perror("ioctl(fd, XFS_IOC_FSBULKSTAT_SINGLE");
+              close(fd);
+              return 1;
+         }
+         close(fd);
+
+         errno = 0;
+         if ((fd = jdm_open(fshandle, &bstat, O_RDWR)) != -1) {
+              fprintf(stderr, "jdm_open(%s, O_RDWR) did not fail\n", path);
+              fail++;
+              close(fd);
+         } else if (errno != EPERM) {
+              perror("jdm_open");
+              fprintf(stderr, "jdm_open(%s, O_RDWR) did not set errno == EPERM\n", path);
+              fail++;
+         }
+
+         errno = 0;
+         if ((fd = jdm_open(fshandle, &bstat, O_WRONLY)) != -1) {
+              fprintf(stderr, "jdm_open(%s, O_WRONLY) did not fail\n", path);
+              fail++;
+              close(fd);
+         } else if (errno != EPERM) {
+              fprintf(stderr, "jdm_open(%s, O_WRONLY) did not set errno == EPERM\n", path);
+              fail++;
+         }
+
+         errno = 0;
+         if ((fd = jdm_open(fshandle, &bstat, O_RDWR|O_TRUNC)) != -1) {
+              fprintf(stderr, "jdm_open(%s, O_RDWR|O_TRUNC) did not fail\n", path);
+              fail++;
+              close(fd);
+         } else if (errno != EPERM) {
+              fprintf(stderr, "jdm_open(%s, O_RDWR|O_TRUNC) did not set errno == EPERM\n", path);
+              fail++;
+         }
+
+         errno = 0;
+         if ((fd = jdm_open(fshandle, &bstat, O_WRONLY|O_TRUNC)) != -1) {
+              fprintf(stderr, "jdm_open(%s, O_WRONLY|O_TRUNC) did not fail\n", path);
+              fail++;
+              close(fd);
+         } else if (errno != EPERM) {
+              fprintf(stderr, "jdm_open(%s, O_WRONLY|O_TRUNC did not set errno == EPERM\n", path);
+              fail++;
+         }
+
+         errno = 0;
+         if ((fd = jdm_open(fshandle, &bstat, O_RDWR|O_APPEND|O_TRUNC)) != -1) {
+              fprintf(stderr, "jdm_open(%s, O_RDWR|O_APPEND|O_TRUNC) did not fail\n", path);
+              fail++;
+              close(fd);
+         } else if (errno != EPERM) {
+              fprintf(stderr, "jdm_open(%s, O_RDWR|O_APPEND|O_TRUNC) did not set errno == EPERM\n", path);
+              fail++;
+         }
+
+         errno = 0;
+         if ((fd = jdm_open(fshandle, &bstat, O_WRONLY|O_APPEND|O_TRUNC)) != -1) {
+              fprintf(stderr, "jdm_open(%s, O_WRONLY|O_APPEND|O_TRUNC) did not fail\n", path);
+              fail++;
+              close(fd);
+         } else if (errno != EPERM) {
+              fprintf(stderr, "jdm_open(%s, O_WRONLY|O_APPEND|O_TRUNC) did not set errno == EPERM\n", path);
+              fail++;
+         }
+     }
+
+#ifdef TEST_UTIME
+     errno = 0;
+     if (utime(path, &tbuf) != -1) {
+         fprintf(stderr, "utime(%s, <epoch>) did not fail\n", path);
+         fail++;
+     } else if (errno != EPERM) {
+         fprintf(stderr, "utime(%s, <epoch>) did not set errno == EPERM\n", path);
+         fail++;
+     }
+
+     errno = 0;
+     if (utime(path, NULL) == -1) {
+          fprintf(stderr, "utime(%s, NULL) failed\n", path);
+          fail++;
+     }
+#endif /* TEST_UTIME */
+
+     asprintf(&linkpath, "%s/append-only.f.hardlink", dir);
+     errno = 0;
+     if (link(path, linkpath) != -1) {
+         fprintf(stderr, "link(%s, %s) did not fail\n", path, linkpath);
+         fail++;
+         unlink(linkpath);
+     } else if (errno != EPERM) {
+         fprintf(stderr, "link(%s, %s) did not set errno == EPERM\n", path, linkpath);
+         fail++;
+     }
+     free(linkpath);
+
+     if (!getuid()) { /* these would fail if not root anyway */
+         errno = 0;
+         if (chmod(path, 7777) != -1) {
+              fprintf(stderr, "chmod(%s, 7777) did not fail\n", path);
+              fail++;
+              chmod(path, 0666);
+         } else if (errno != EPERM) {
+              fprintf(stderr, "chmod(%s, 7777) did not set errno == EPERM\n", path);
+              fail++;
+         }
+
+         errno = 0;
+         if (chown(path, 1, 1) != -1) {
+              fprintf(stderr, "chown(%s, 1, 1) did not fail\n", path);
+              fail++;
+              chown(path, 0, 0);
+         } else if (errno != EPERM) {
+              fprintf(stderr, "chown(%s, 1, 1) did not set errno to EPERM\n", path);
+              fail++;
+         }
+         errno = 0;
+         if (del_acl(path) != 1) {
+              if (errno != EOPNOTSUPP) {
+              fprintf(stderr, "del_acl(%s) did not fail\n", path);
+              fail++;
+              }
+         } else if (errno != EPERM) {
+              fprintf(stderr, "del_acl(%s) did not set errno == EPERM\n", path);
+              fail++;
+         }
+         errno = 0;
+         if (add_acl(path, acl_text) != 1) {
+              if (errno != EOPNOTSUPP) {
+              fprintf(stderr, "add_acl(%s) did not fail\n", path);
+              fail++;
+              }
+         } else if (errno != EPERM) {
+              fprintf(stderr, "add_acl(%s) did not set errno == EPERM\n", path);
+               fail++;
+          }
+
+          if (setxattr(path, "trusted.test", "scribble", strlen("scribble"), XATTR_REPLACE) != -1) {
+               fprintf(stderr, "setxattr(%s, trusted.test, scribble, 8, XATTR_REPLACE) did not fail\n", path);
+               fail++;
+          } else if (errno != EPERM && errno != EOPNOTSUPP) {
+               fprintf(stderr,
+                       "setxattr(%s, trusted.test, scribble, 8, XATTR_REPLACE) did not set errno == EPERM\n", path);
+               fail++;
+          }
+          if (setxattr(path, "trusted.scribble", "scribble", strlen("scribble"), XATTR_CREATE) != -1) {
+               fprintf(stderr, "setxattr(%s, trusted.scribble, scribble, 8, XATTR_CREATE) did not fail\n", path);
+               fail++;
+          } else if (errno != EPERM && errno != EOPNOTSUPP) {
+               fprintf(stderr,
+                       "setxattr(%s, trusted.scribble, scribble, 8, XATTR_CREATE) did not set errno == EPERM\n", path);
+               fail++;
+          }
+          if (removexattr(path, "trusted.test") != -1) {
+               fprintf(stderr, "removexattr(%s, trusted.test) did not fail\n", path);
+               fail++;
+          } else if (errno != EPERM && errno != EOPNOTSUPP) {
+               fprintf(stderr,
+                       "removexattr(%s, trusted.test) did not set errno == EPERM\n", path);
+               fail++;
+          }
+     }
+
+     if (setxattr(path, "user.test", "scribble", strlen("scribble"), XATTR_REPLACE) != -1) {
+          fprintf(stderr, "setxattr(%s, user.test, scribble, 8, XATTR_REPLACE) did not fail\n", path);
+          fail++;
+     } else if (errno != EPERM && errno != EOPNOTSUPP) {
+          fprintf(stderr, "setxattr(%s, user.test, scribble, 8, XATTR_REPLACE) did not set errno == EPERM\n", path);
+          fail++;
+     }
+     if (setxattr(path, "user.scribble", "scribble", strlen("scribble"), XATTR_CREATE) != -1) {
+          fprintf(stderr, "setxattr(%s, user.scribble, scribble, 8, XATTR_CREATE) did not fail\n", path);
+          fail++;
+     } else if (errno != EPERM && errno != EOPNOTSUPP) {
+          fprintf(stderr, "setxattr(%s, user.scribble, scribble, 8, XATTR_CREATE) did not set errno == EPERM\n", path);
+          fail++;
+     }
+     if (removexattr(path, "user.test") != -1) {
+          fprintf(stderr, "removexattr(%s, user.test) did not fail\n", path);
+          fail++;
+     } else if (errno != EPERM && errno != EOPNOTSUPP) {
+          fprintf(stderr,
+                  "removexattr(%s, user.test) did not set errno == EPERM\n", path);
+          fail++;
+     }
+
+     asprintf(&linkpath, "%s/append-only.f.newname", dir);
+     errno = 0;
+     if (rename(path, linkpath) != -1) {
+         fprintf(stderr, "rename(%s, %s) did not fail\n", path, linkpath);
+         fail++;
+         rename(linkpath, path);
+     } else if (errno != EPERM) {
+         fprintf(stderr, "rename(%s, %s) did not set errno == EPERM\n", path, linkpath);
+         fail++;
+     }
+     free(linkpath);
+
+     if ((fd = open(path, O_RDONLY)) == -1) {
+         fprintf(stderr, "open(%s, O_RDONLY) failed: %s\n", path, strerror(errno));
+         fail++;
+     } else {
+         if (fstat(fd, &st) == -1) {
+              perror("fstat");
+              fail++;
+         } else if (st.st_size) {
+              origsize = st.st_size;
+              if ((orig = malloc(st.st_size)) == NULL)
+                   perror("malloc");
+              else {
+                   if (lseek(fd, 0, SEEK_SET) == -1) {
+                        perror("lseek(fd, 0, SEEK_SET) failed");
+                        fail++;
+                   }
+                   if (read(fd, orig, st.st_size) != st.st_size) {
+                        perror("read failed");
+                        fail++;
+                   }
+              }
+         }
+         close(fd);
+     }
+
+     if ((fd = open(path, O_WRONLY|O_APPEND)) == -1) {
+         fprintf(stderr, "open(%s, O_WRONLY|O_APPEND) failed: %s\n", path, strerror(errno));
+         fail++;
+     } else {
+         lseek(fd, 0, SEEK_SET); /* this is silently ignored */
+         if (write(fd, scribble, strlen(scribble)) != strlen(scribble)) {
+              fprintf(stderr, "write(%s, %s, %d) failed: %s\n", path, scribble, strlen(scribble),
+                      strerror(errno));
+              fail++;
+         }
+         lseek(fd, origsize, SEEK_SET); /* this is silently ignored */
+          if (write(fd, scribble, strlen(scribble)) != strlen(scribble)) {
+               fprintf(stderr, "write(%s, %s, %d) failed: %s\n", path, scribble, strlen(scribble),
+                       strerror(errno));
+               fail++;
+          }
+         close(fd);
+         if ((fd = open(path, O_RDONLY)) == -1) { /* now we check to make sure lseek() ignored us */
+              fprintf(stderr, "open(%s, O_RDONLY) failed: %s\n", path, strerror(errno));
+              fail++;
+         } else {
+              if (lseek(fd, 0, SEEK_SET) == -1) {
+                   fprintf(stderr, "lseek(%s, 0, SEEK_SET) failed: %s\n", path, strerror(errno));
+                   fail++;
+              } else if (origsize) {
+                   if ((buf = malloc(origsize)) == NULL)
+                        perror("malloc");
+                   else {
+                        if (read(fd, buf, origsize) == -1) {
+                             fprintf(stderr, "read(%s, &buf, %ld) failed: %s\n", path, (long)origsize, strerror(errno));
+                             fail++;
+                        } else {
+                             if (memcmp(orig, buf, origsize)) {
+                                  fprintf(stderr, "existing data in append-only.f was overwritten\n");
+                                  fail++;
+                             }
+                        }
+                        free(buf);
+                   }
+              }
+              if (lseek(fd, origsize, SEEK_SET) == -1) {
+                    fprintf(stderr, "lseek(%s, %ld, SEEK_SET) failed: %s\n", path, (long)origsize, strerror(errno));
+                    fail++;
+               } else {
+                   if ((buf = malloc(strlen(scribble2))) == NULL)
+                         perror("malloc");
+                    else {
+                         if (read(fd, buf, strlen(scribble2)) == -1) {
+                              fprintf(stderr, "read(%s, &buf, %d) failed: %s\n", path,
+                                     strlen(scribble2), strerror(errno));
+                              fail++;
+                         } else {
+                              if (memcmp(scribble2, buf, strlen(scribble2))) {
+                                   fprintf(stderr, "existing data in append-only.f was overwritten\n");
+                                   fail++;
+                              }
+                         }
+                         free(buf);
+                    }
+                   close(fd);
+              }
+         }
+     }
+
+     if ((fd = open(path, O_RDWR|O_APPEND)) == -1) {
+         fprintf(stderr, "open(%s, O_RDWR|O_APPEND) failed: %s\n", path, strerror(errno));
+         fail++;
+     } else {
+         lseek(fd, 0, SEEK_SET); /* this is silently ignored */
+         if (write(fd, scribble, strlen(scribble)) != strlen(scribble)) {
+              fprintf(stderr, "write(%s, %s, %d) failed: %s\n", path, scribble, strlen(scribble),
+                      strerror(errno));
+              fail++;
+         }
+         lseek(fd, origsize, SEEK_SET); /* this is silently ignored */
+          if (write(fd, scribble, strlen(scribble)) != strlen(scribble)) {
+               fprintf(stderr, "write(%s, %s, %d) failed: %s\n", path, scribble, strlen(scribble),
+                       strerror(errno));
+               fail++;
+          }
+         close(fd);
+         if ((fd = open(path, O_RDONLY)) == -1) { /* now we check to make sure lseek() ignored us */
+              fprintf(stderr, "open(%s, O_RDONLY) failed: %s\n", path, strerror(errno));
+              fail++;
+         } else {
+              if (lseek(fd, 0, SEEK_SET) == -1) {
+                   fprintf(stderr, "lseek(%s, 0, SEEK_SET) failed: %s\n", path, strerror(errno));
+                   fail++;
+              } else if (origsize) {
+                   if ((buf = malloc(origsize)) == NULL)
+                        perror("malloc");
+                   else {
+                        if (read(fd, buf, origsize) == -1) {
+                             fprintf(stderr, "read(%s, &buf, %ld) failed: %s\n", path, (long)origsize, strerror(errno));
+                             fail++;
+                        } else {
+                             if (memcmp(orig, buf, origsize)) {
+                                  fprintf(stderr, "existing data in append-only.f was overwritten\n");
+                                  fail++;
+                             }
+                        }
+                        free(buf);
+                        free(orig);
+                   }
+              }
+              if (lseek(fd, origsize, SEEK_SET) == -1) {
+                    fprintf(stderr, "lseek(%s, %ld, SEEK_SET) failed: %s\n", path, (long)origsize, strerror(errno));
+                    fail++;
+               } else if (scribble4) {
+                   if ((buf = malloc(strlen(scribble4))) == NULL)
+                         perror("malloc");
+                    else {
+                         if (read(fd, buf, strlen(scribble4)) == -1) {
+                              fprintf(stderr, "read(%s, &buf, %d) failed: %s\n", path,
+                                     strlen(scribble4), strerror(errno));
+                              fail++;
+                         } else {
+                              if (memcmp(scribble4, buf, strlen(scribble4))) {
+                                   fprintf(stderr, "existing data in append-only.f was overwritten\n");
+                                   fail++;
+                              }
+                         }
+                         free(buf);
+                    }
+                   close(fd);
+              }
+         }
+     }
+
+     if (stfs.f_type == XFS_SUPER_MAGIC && !getuid()) {
+          jdm_fshandle_t *fshandle;
+          xfs_bstat_t bstat;
+          xfs_fsop_bulkreq_t  bulkreq;
+          xfs_ino_t ino;
+          char *dirpath;
+
+         if ((fd = open(path, O_RDONLY)) == -1) {
+              fprintf(stderr, "open(%s, O_RDONLY) failed: %s\n", path, strerror(errno));
+              fail++;
+         } else {
+              if (fstat(fd, &st) == -1) {
+                   perror("fstat");
+                   fail++;
+              } else if (st.st_size) {
+                   origsize = st.st_size;
+                   if ((orig = malloc(st.st_size)) == NULL)
+                        perror("malloc");
+                   else {
+                        if (lseek(fd, 0, SEEK_SET) == -1) {
+                             perror("lseek(fd, 0, SEEK_SET) failed");
+                             fail++;
+                        }
+                        if (read(fd, orig, st.st_size) != st.st_size) {
+                             perror("read failed");
+                             fail++;
+                        }
+                   }
+              }
+              close(fd);
+         }
+
+          dirpath = strdup(path); /* dirname obnoxiously modifies its arg */
+          if ((fshandle = jdm_getfshandle(dirname(dirpath))) == NULL) {
+               perror("jdm_getfshandle");
+               return 1;
+          }
+          free(dirpath);
+
+          if (stat(path, &st) != 0) {
+               perror("stat");
+               return 1;
+          }
+
+          ino = st.st_ino;
+
+          bulkreq.lastip = &ino;
+          bulkreq.icount = 1;
+          bulkreq.ubuffer = &bstat;
+          bulkreq.ocount = NULL;
+
+          if ((fd = open(path, O_RDONLY)) == -1) {
+               perror("open");
+               return 1;
+          }
+
+          if (ioctl(fd, XFS_IOC_FSBULKSTAT_SINGLE, &bulkreq) == -1) {
+               perror("ioctl(fd, XFS_IOC_FSBULKSTAT_SINGLE");
+               close(fd);
+               return 1;
+          }
+          close(fd);
+
+         if ((fd = jdm_open(fshandle, &bstat, O_WRONLY|O_APPEND)) == -1) {
+              fprintf(stderr, "jdm_open(%s, O_WRONLY|O_APPEND) failed: %s\n", path, strerror(errno));
+              fail++;
+         } else {
+              lseek(fd, 0, SEEK_SET); /* this is silently ignored */
+              if (write(fd, scribble, strlen(scribble)) != strlen(scribble)) {
+                   fprintf(stderr, "write(%s, %s, %d) failed: %s\n", path, scribble, strlen(scribble),
+                           strerror(errno));
+                   fail++;
+              }
+              lseek(fd, origsize, SEEK_SET); /* this is silently ignored */
+              if (write(fd, scribble, strlen(scribble)) != strlen(scribble)) {
+                   fprintf(stderr, "write(%s, %s, %d) failed: %s\n", path, scribble, strlen(scribble),
+                           strerror(errno));
+                   fail++;
+              }
+              close(fd);
+              if ((fd = jdm_open(fshandle, &bstat, O_RDONLY)) == -1) { /* now we check to make sure lseek() ignored us */
+                   fprintf(stderr, "jdm_open(%s, O_RDONLY) failed: %s\n", path, strerror(errno));
+                   fail++;
+              } else {
+                   if (lseek(fd, 0, SEEK_SET) == -1) {
+                        fprintf(stderr, "lseek(%s, 0, SEEK_SET) failed: %s\n", path, strerror(errno));
+                        fail++;
+                   } else if (origsize) {
+                        if ((buf = malloc(origsize)) == NULL)
+                             perror("malloc");
+                        else {
+                             if (read(fd, buf, origsize) == -1) {
+                                  fprintf(stderr, "read(%s, &buf, %ld) failed: %s\n", path, (long)origsize, strerror(errno));
+                                  fail++;
+                             } else {
+                                  if (memcmp(orig, buf, origsize)) {
+                                       fprintf(stderr, "existing data in append-only.f was overwritten\n");
+                                       fail++;
+                                  }
+                             }
+                             free(buf);
+                        }
+                   }
+                   if (lseek(fd, origsize, SEEK_SET) == -1) {
+                        fprintf(stderr, "lseek(%s, %ld, SEEK_SET) failed: %s\n", path, (long)origsize, strerror(errno));
+                        fail++;
+                   } else if (strlen(scribble2)) {
+                        if ((buf = malloc(strlen(scribble2))) == NULL)
+                             perror("malloc");
+                        else {
+                             if (read(fd, buf, strlen(scribble2)) == -1) {
+                                  fprintf(stderr, "read(%s, &buf, %d) failed: %s\n", path,
+                                          strlen(scribble2), strerror(errno));
+                                  fail++;
+                             } else {
+                                  if (memcmp(scribble2, buf, strlen(scribble2))) {
+                                       fprintf(stderr, "existing data in append-only.f was overwritten\n");
+                                       fail++;
+                                  }
+                             }
+                             free(buf);
+                        }
+                        close(fd);
+                   }
+              }
+         }
+
+         if ((fd = jdm_open(fshandle, &bstat, O_RDWR|O_APPEND)) == -1) {
+              fprintf(stderr, "jdm_open(%s, O_RDWR|O_APPEND) failed: %s\n", path, strerror(errno));
+              fail++;
+         } else {
+              lseek(fd, 0, SEEK_SET); /* this is silently ignored */
+              if (write(fd, scribble, strlen(scribble)) != strlen(scribble)) {
+                   fprintf(stderr, "write(%s, %s, %d) failed: %s\n", path, scribble, strlen(scribble),
+                           strerror(errno));
+                   fail++;
+              }
+              lseek(fd, origsize, SEEK_SET); /* this is silently ignored */
+              if (write(fd, scribble, strlen(scribble)) != strlen(scribble)) {
+                   fprintf(stderr, "write(%s, %s, %d) failed: %s\n", path, scribble, strlen(scribble),
+                           strerror(errno));
+                   fail++;
+              }
+              close(fd);
+              if ((fd = jdm_open(fshandle, &bstat, O_RDONLY)) == -1) { /* now we check to make sure lseek() ignored us */
+                   fprintf(stderr, "jdm_open(%s, O_RDONLY) failed: %s\n", path, strerror(errno));
+                   fail++;
+              } else {
+                   if (lseek(fd, 0, SEEK_SET) == -1) {
+                        fprintf(stderr, "lseek(%s, 0, SEEK_SET) failed: %s\n", path, strerror(errno));
+                        fail++;
+                   } else if (origsize) {
+                        if ((buf = malloc(origsize)) == NULL)
+                             perror("malloc");
+                        else {
+                             if (read(fd, buf, origsize) == -1) {
+                                  fprintf(stderr, "read(%s, &buf, %ld) failed: %s\n", path, (long)origsize, strerror(errno));
+                                  fail++;
+                             } else {
+                                  if (memcmp(orig, buf, origsize)) {
+                                       fprintf(stderr, "existing data in append-only.f was overwritten\n");
+                                       fail++;
+                                  }
+                             }
+                             free(buf);
+                             free(orig);
+                        }
+                   }
+                   if (lseek(fd, origsize, SEEK_SET) == -1) {
+                        fprintf(stderr, "lseek(%s, %ld, SEEK_SET) failed: %s\n", path, (long)origsize, strerror(errno));
+                        fail++;
+                   } else if (strlen(scribble4)) {
+                        if ((buf = malloc(strlen(scribble4))) == NULL)
+                             perror("malloc");
+                        else {
+                             if (read(fd, buf, strlen(scribble4)) == -1) {
+                                  fprintf(stderr, "read(%s, &buf, %d) failed: %s\n", path,
+                                          strlen(scribble4), strerror(errno));
+                                  fail++;
+                             } else {
+                                  if (memcmp(scribble4, buf, strlen(scribble4))) {
+                                       fprintf(stderr, "existing data in append-only.f was overwritten\n");
+                                       fail++;
+                                  }
+                             }
+                             free(buf);
+                        }
+                        close(fd);
+                   }
+              }
+         }
+     }
+
+     errno = 0;
+     if (unlink(path) != -1) {
+         fprintf(stderr, "unlink(%s) did not fail\n", path);
+         fail++;
+     } else if (errno != EPERM) {
+         fprintf(stderr, "unlink(%s) did not set errno == EPERM\n", path);
+         fail ++;
+     }
+
+     free(path);
+     asprintf(&path, "%s/append-only.d/file", dir);
+     if ((fd = open(path, O_RDWR)) == -1) {
+         fprintf(stderr, "open(%s, O_RDWR) failed: %s\n", path, strerror(errno));
+         fail++;
+     } else {
+         if (write(fd, scribble, strlen(scribble)) != strlen(scribble)) {
+              fprintf(stderr, "write(%s, %s, %d) failed: %s\n", path, scribble, strlen(scribble), strerror(errno));
+              fail++;
+         }
+         close(fd);
+     }
+     if (!getuid) {
+         if (chmod(path, 0777) == -1) {
+              fprintf(stderr, "chmod(%s, 0777) failed: %s\n", path, strerror(errno));
+              fail++;
+         } else
+              chmod(path, 0666);
+         if (chown(path, 1, 1) == -1) {
+              fprintf(stderr, "chown(%s, 1, 1) failed: %s\n", path, strerror(errno));
+              fail++;
+         } else
+              chown(path, 0, 0);
+     }
+
+     asprintf(&linkpath, "%s/append-only.d/file.link-%d", dir, getuid());
+     errno = 0;
+     if (link(path, linkpath) == -1) { 
+         fprintf(stderr, "link(%s, %s) failed: %s\n", path, linkpath, strerror(errno));
+         fail++;
+     } else if (unlink(linkpath) != -1) {
+         fprintf(stderr, "unlink(%s) did not fail\n", linkpath);
+         fail++;
+     }
+     free(linkpath);
+     asprintf(&linkpath, "%s/append-only.d/file.symlink-%d", dir, getuid());
+     if (symlink(path, linkpath) == -1) {
+         fprintf(stderr, "symlink(%s, %s) failed: %s\n", path, linkpath, strerror(errno));
+         fail++;
+     } else if (unlink(linkpath) != -1) {
+         fprintf(stderr, "unlink(%s) did not fail\n", linkpath);
+          fail++;
+     }
+
+     free(linkpath);
+     asprintf(&linkpath, "%s/append-only.d/file.newname", dir);
+     if (rename(path, linkpath) != -1) {
+         fprintf(stderr, "rename(%s, %s) did not fail\n", path, linkpath);
+         fail++;
+         rename(linkpath, path);
+     } else if (errno != EPERM) {
+         fprintf(stderr, "rename(%s, %s) did not set errno == EPERM\n", path, linkpath);
+          fail++;
+     }
+     free(linkpath);
+
+     if (unlink(path) != -1) {
+         fprintf(stderr, "unlink(%s) did not fail\n", path);
+         fail++;
+     } else if (errno != EPERM) {
+         fprintf(stderr, "unlink(%s) did not set errno == EPERM\n", path);
+          fail++;
+     }
+
+     free(path);
+     asprintf(&path, "%s/append-only.d/newfile-%d", dir, getuid());
+     errno = 0;
+     if ((fd = open(path, O_RDWR|O_CREAT, 0666)) == -1) {
+         fprintf(stderr, "open(%s, O_RDWR|O_CREAT, 0666) failed: %s\n", path, strerror(errno));
+         fail++;
+     } else if (unlink(path) != -1) {
+         fprintf(stderr, "unlink(%s) did not fail\n", path);
+         fail++;
+         close(fd);
+     } else
+         close(fd);
+
+     if (!getuid()) {
+         free(path);
+         asprintf(&path, "%s/append-only.d/newdev-%d", dir, getuid());
+         if (stat("/dev/null", &st) != -1) {
+              if (mknod(path, S_IFCHR|S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH, st.st_rdev) == -1) {
+                   fprintf(stderr, "mknod(%s, S_IFCHR|0666, %lld) failed: %s\n", path, st.st_rdev, strerror(errno));
+                   fail++;
+              } else if (unlink(path) != -1) { 
+                   fprintf(stderr, "unlink(%s) did not fail\n", path);
+                   fail++;
+              }
+         }
+     }
+
+     free(path);
+     asprintf(&path, "%s/append-only.d/newdir-%d", dir, getuid());
+     if (mkdir(path, 0777) == -1) {
+         fprintf(stderr, "mkdir(%s, 0777) failed: %s\n", path, strerror(errno));
+         fail++;
+     } else if (rmdir(path) != -1) {
+         fprintf(stderr, "rmdir(%s) did not fail\n", path);
+          fail++;
+     }
+     
+     free(path);
+     asprintf(&path, "%s/append-only.d/newdir-%d/newfile-%d", dir, getuid(), getuid());
+     if ((fd = open(path, O_RDWR|O_CREAT, 0666)) == -1) {
+          fprintf(stderr, "open(%s, O_RDWR|O_CREAT, 0666) failed: %s\n", path, strerror(errno));
+          fail++;
+     } else {
+          if (write(fd, scribble, strlen(scribble)) != strlen(scribble)) {
+               fprintf(stderr, "write(%s, %s, %d) failed: %s\n", path, scribble, strlen(scribble), strerror(errno));
+               fail++;
+          }
+          close(fd);
+     }
+     if (!getuid()) {
+         if (chmod(path, 0700) == -1) {
+              fprintf(stderr, "chmod(%s, 0700) failed: %s\n", path, strerror(errno));
+              fail++;
+         } else
+              chmod(path, 0666);
+
+          if (chown(path, 1, 1) == -1) {
+               fprintf(stderr, "chown(%s, 1, 1) failed: %s\n", path, strerror(errno));
+               fail++;
+          } else
+               chown(path, 0, 0);
+     }
+
+     free(path);
+     asprintf(&path, "%s/append-only.d/dir/newfile-%d", dir, getuid());
+     if ((fd = open(path, O_RDWR|O_CREAT, 0666)) == -1) {
+          fprintf(stderr, "open(%s, O_RDWR|O_CREAT, 0666) failed: %s\n", path, strerror(errno));
+          fail++;
+     } else {
+          if (write(fd, scribble, strlen(scribble)) != strlen(scribble)) {
+               fprintf(stderr, "write(%s, %s, %d) failed: %s\n", path, scribble, strlen(scribble), strerror(errno));
+               fail++;
+          }
+          close(fd);
+     }
+     if (!getuid()) {
+         if (chmod(path, 0700) == -1) {
+              fprintf(stderr, "chmod(%s, 0700) failed: %s\n", path, strerror(errno));
+              fail++;
+         } else
+              chmod(path, 0666);
+
+          if (chown(path, 1, 1) == -1) {
+               fprintf(stderr, "chown(%s, 1, 1) failed: %s\n", path, strerror(errno));
+               fail++;
+          } else
+               chown(path, 0, 0);
+     }
+
+     free(path);
+     asprintf(&path, "%s/append-only.d/dir", dir);
+     errno = 0;
+     if (rmdir(path) != -1) {
+         fprintf(stderr, "rmdir(%s) did not fail\n", path);
+         fail++;
+     } else if (errno != EPERM) {
+         fprintf(stderr, "rmdir(%s) did not set errno == EPERM\n", path);
+         fail++;
+     }
+
+     free(path);
+     asprintf(&path, "%s/append-only.d", dir);
+#ifdef TEST_UTIME
+     errno = 0;
+     if (utime(path, &tbuf) != -1) {
+         fprintf(stderr, "utime(%s, <epoch>) did not fail\n", path);
+         fail++;
+     } else if (errno != EPERM) {
+         fprintf(stderr, "utime(%s, <epoch>) did not set errno == EPERM\n", path);
+         fail++;
+     }
+
+     errno = 0;
+     if (utime(path, NULL) == -1) {
+          fprintf(stderr, "utime(%s, NULL) failed\n", path);
+          fail++;
+     }
+#endif /* TEST_UTIME */
+
+     if (!getuid()) { /* these would fail if not root anyway */
+         errno = 0;
+         if (chmod(path, 7777) != -1) {
+              fprintf(stderr, "chmod(%s, 7777) did not fail\n", path);
+              fail++;
+              chmod(path, 0666);
+         } else if (errno != EPERM) {
+              fprintf(stderr, "chmod(%s, 7777) did not set errno == EPERM\n", path);
+              fail++;
+         }
+
+         errno = 0;
+         if (chown(path, 1, 1) != -1) {
+              fprintf(stderr, "chown(%s, 1, 1) did not fail\n", path);
+              fail++;
+              chown(path, 0, 0);
+         } else if (errno != EPERM) {
+              fprintf(stderr, "chown(%s, 1, 1) did not set errno to EPERM\n", path);
+              fail++;
+         }
+         errno = 0;
+         if (del_acl(path) != 1) {
+              if (errno != EOPNOTSUPP) {
+              fprintf(stderr, "del_acl(%s) did not fail\n", path);
+              fail++;
+              }
+         } else if (errno != EPERM) {
+              fprintf(stderr, "del_acl(%s) did not set errno == EPERM\n", path);
+              fail++;
+         }
+         errno = 0;
+         if (add_acl(path, acl_text) != 1) {
+              if (errno != EOPNOTSUPP) {
+              fprintf(stderr, "add_acl(%s) did not fail\n", path);
+              fail++;
+              }
+         } else if (errno != EPERM) {
+              fprintf(stderr, "add_acl(%s) did not set errno == EPERM\n", path);
+               fail++;
+          }
+
+          if (setxattr(path, "trusted.test", "scribble", strlen("scribble"), XATTR_REPLACE) != -1) {
+               fprintf(stderr, "setxattr(%s, trusted.test, scribble, 8, XATTR_REPLACE) did not fail\n", path);
+               fail++;
+          } else if (errno != EPERM && errno != EOPNOTSUPP) {
+               fprintf(stderr,
+                       "setxattr(%s, trusted.test, scribble, 8, XATTR_REPLACE) did not set errno == EPERM\n", path);
+               fail++;
+          }
+          if (setxattr(path, "trusted.scribble", "scribble", strlen("scribble"), XATTR_CREATE) != -1) {
+               fprintf(stderr, "setxattr(%s, trusted.scribble, scribble, 8, XATTR_CREATE) did not fail\n", path);
+               fail++;
+          } else if (errno != EPERM && errno != EOPNOTSUPP) {
+               fprintf(stderr,
+                       "setxattr(%s, trusted.scribble, scribble, 8, XATTR_CREATE) did not set errno == EPERM\n", path);
+               fail++;
+          }
+          if (removexattr(path, "trusted.test") != -1) {
+               fprintf(stderr, "removexattr(%s, trusted.test) did not fail\n", path);
+               fail++;
+          } else if (errno != EPERM && errno != EOPNOTSUPP) {
+               fprintf(stderr,
+                       "removexattr(%s, trusted.test) did not set errno == EPERM\n", path);
+               fail++;
+          }
+     }
+
+     if (setxattr(path, "user.test", "scribble", strlen("scribble"), XATTR_REPLACE) != -1) {
+          fprintf(stderr, "setxattr(%s, user.test, scribble, 8, XATTR_REPLACE) did not fail\n", path);
+          fail++;
+     } else if (errno != EPERM && errno != EOPNOTSUPP) {
+          fprintf(stderr, "setxattr(%s, user.test, scribble, 8, XATTR_REPLACE) did not set errno == EPERM\n", path);
+          fail++;
+     }
+     if (setxattr(path, "user.scribble", "scribble", strlen("scribble"), XATTR_CREATE) != -1) {
+          fprintf(stderr, "setxattr(%s, user.scribble, scribble, 8, XATTR_CREATE) did not fail\n", path);
+          fail++;
+     } else if (errno != EPERM && errno != EOPNOTSUPP) {
+          fprintf(stderr, "setxattr(%s, user.scribble, scribble, 8, XATTR_CREATE) did not set errno == EPERM\n", path);
+          fail++;
+     }
+     if (removexattr(path, "user.test") != -1) {
+          fprintf(stderr, "removexattr(%s, user.test) did not fail\n", path);
+          fail++;
+     } else if (errno != EPERM && errno != EOPNOTSUPP) {
+         perror("removexattr");
+          fprintf(stderr,
+                  "removexattr(%s, user.test) did not set errno == EPERM\n", path);
+          fail++;
+     }
+
+     free(path);
+     asprintf(&path, "%s/empty-append-only.d", dir);
+     errno = 0;
+     if (rmdir(path) != -1) {
+          fprintf(stderr, "rmdir(%s) did not fail\n", path);
+          fail++;
+     } else if (errno != EPERM) {
+          fprintf(stderr, "rmdir(%s) did not set errno == EPERM\n", path);
+          fail++;
+     }  
+
+     free(path);
+     return fail;
+}
+
+static int check_test_area(const char *dir)
+{
+     char *path;
+     struct stat st;
+
+     asprintf(&path, "%s/", dir);
+     if (stat(path, &st) == -1) {
+         fprintf(stderr, "%s: %s: %s\n", __progname, path, strerror(errno));
+         return 1;
+     }
+     if (!(st.st_mode & (S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IWOTH|S_IXOTH)) || st.st_uid) {
+         fprintf(stderr, "%s: %s needs to be rwx for for all, and owner uid should be 0\n",
+                 __progname, path);
+         return 1;
+     }
+
+     free(path);
+     asprintf(&path, "%s/immutable.f", dir);
+     if (stat(path, &st) == -1) {
+         perror(path);
+         return 1;
+     }
+     if (!(st.st_mode & (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)) || st.st_uid || !S_ISREG(st.st_mode)) {
+         fprintf(stderr, "%s: %s needs to be a regular file, rw for all, and owner uid should be 0\n",
+                 __progname, path);
+         return 1;
+     }
+
+     free(path);
+     asprintf(&path, "%s/append-only.f", dir);
+     if (stat(path, &st) == -1) {
+          perror(path);
+          return 1;
+     }
+     if (!(st.st_mode & (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)) || st.st_uid || !S_ISREG(st.st_mode)) {
+          fprintf(stderr, "%s: %s needs to be a regular file, rw for all, and owner uid should be 0\n",
+                  __progname, path);
+          return 1;
+     }
+
+     free(path);
+     asprintf(&path, "%s/immutable.d", dir);
+     if (stat(path, &st) == -1) {
+          perror(path);
+          return 1;
+     }
+     if (!(st.st_mode & (S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IWOTH|S_IXOTH)) ||
+        st.st_uid || !S_ISDIR(st.st_mode)) {
+          fprintf(stderr, "%s: %s needs to be a directory, rwx for all, and owner uid should be 0\n",
+                  __progname, path);
+          return 1;
+     }
+
+     free(path);
+     asprintf(&path, "%s/append-only.d", dir);
+     if (stat(path, &st) == -1) {
+          perror(path);
+          return 1;
+     }
+     if (!(st.st_mode & (S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IWOTH|S_IXOTH)) ||
+        st.st_uid || !S_ISDIR(st.st_mode)) {
+          fprintf(stderr, "%s: %s needs to be a directory, rwx for all, and owner uid should be 0\n",
+                  __progname, path);
+          return 1;
+     }
+
+     free(path);
+     asprintf(&path, "%s/immutable.d/file", dir);
+     if (stat(path, &st) == -1) {
+          perror(path);
+          return 1;
+     }
+     if (!(st.st_mode & (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)) || st.st_uid || !S_ISREG(st.st_mode)) {
+          fprintf(stderr, "%s: %s needs to be a regular file, rw for all, and owner uid should be 0\n",
+                  __progname, path);
+          return 1;
+     }
+      
+     free(path);
+     asprintf(&path, "%s/append-only.d/file", dir);
+     if (stat(path, &st) == -1) {
+          perror(path);
+          return 1;
+     }
+     if (!(st.st_mode & (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)) || st.st_uid || !S_ISREG(st.st_mode)) {
+          fprintf(stderr, "%s: %s needs to be a regular file, rw for all, and owner uid should be 0\n",
+                  __progname, path);
+          return 1;
+     }
+
+     free(path);
+     asprintf(&path, "%s/immutable.d/dir", dir);
+     if (stat(path, &st) == -1) {
+          perror(path);
+          return 1;
+     }
+     if (!(st.st_mode & (S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IWOTH|S_IXOTH)) ||
+         st.st_uid || !S_ISDIR(st.st_mode)) {
+          fprintf(stderr, "%s: %s needs to be a directory, rwx for all, and owner uid should be 0\n",
+                  __progname, path);
+          return 1;
+     }
+
+     free(path);
+     asprintf(&path, "%s/append-only.d/dir", dir);
+     if (stat(path, &st) == -1) {
+          perror(path);
+          return 1;
+     }
+     if (!(st.st_mode & (S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IWOTH|S_IXOTH)) ||
+         st.st_uid || !S_ISDIR(st.st_mode)) {
+          fprintf(stderr, "%s: %s needs to be a directory, rwx for all, and owner uid should be 0\n",
+                  __progname, path);
+          return 1;
+     }
+     return 0;
+}
+
+static int create_test_area(const char *dir)
+{
+     int fd;
+     char *path;
+     static const char *acl_u_text = "u::rw-,g::rw-,o::rw-,u:nobody:rw-,m::rw-";
+     static const char *acl_u_text_d = "u::rwx,g::rwx,o::rwx,u:nobody:rwx,m::rwx";
+     struct stat st;
+     static const char *immutable = "This is an immutable file.\nIts contents cannot be altered.\n";
+     static const char *append_only = "This is an append-only file.\nIts contents cannot be altered.\n"
+         "Data can only be appended.\n---\n";
+
+     if (getuid()) {
+         fprintf(stderr, "%s: you are not root, go away.\n", __progname);
+         return 1;
+     }   
+
+     if (stat(dir, &st) == 0) {
+         fprintf(stderr, "%s: Test area directory %s must not exist for test area creation.\n",
+                 __progname, dir);
+         return 1;
+     }
+
+     umask(0000);
+     if (mkdir(dir, 0777) != 0) {
+         fprintf(stderr, "%s: error creating directory %s: %s\n", __progname, dir, strerror(errno));
+         return 1;
+     }
+
+     asprintf(&path, "%s/immutable.d", dir);
+     if (mkdir(path, 0777) != 0) {
+          fprintf(stderr, "%s: error creating directory %s: %s\n", __progname, path, strerror(errno));
+          return 1;
+     }
+     free(path);
+
+     asprintf(&path, "%s/empty-immutable.d", dir); 
+     if (mkdir(path, 0777) != 0) {
+          fprintf(stderr, "%s: error creating directory %s: %s\n", __progname, path, strerror(errno));
+          return 1;
+     }
+     free(path);
+
+     asprintf(&path, "%s/append-only.d", dir); 
+     if (mkdir(path, 0777) != 0) {
+          fprintf(stderr, "%s: error creating directory %s: %s\n", __progname, path, strerror(errno));
+          return 1;
+     }
+     free(path);
+
+     asprintf(&path, "%s/empty-append-only.d", dir); 
+     if (mkdir(path, 0777) != 0) {
+          fprintf(stderr, "%s: error creating directory %s: %s\n", __progname, path, strerror(errno));
+          return 1;
+     }
+     free(path);
+
+     asprintf(&path, "%s/immutable.d/dir", dir); 
+     if (mkdir(path, 0777) != 0) {
+          fprintf(stderr, "%s: error creating directory %s: %s\n", __progname, path, strerror(errno));
+          return 1;
+     }
+     free(path);
+
+     asprintf(&path, "%s/append-only.d/dir", dir); 
+     if (mkdir(path, 0777) != 0) {
+          fprintf(stderr, "%s: error creating directory %s: %s\n", __progname, path, strerror(errno));
+          return 1;
+     }
+     free(path);
+
+     asprintf(&path, "%s/append-only.d/file", dir);
+     if ((fd = open(path, O_WRONLY|O_CREAT|O_EXCL, 0666)) == -1) {
+         fprintf(stderr, "%s: error creating file %s: %s\n", __progname, path, strerror(errno));
+          return 1;
+     }
+     close(fd);
+     free(path);
+
+     asprintf(&path, "%s/immutable.d/file", dir);
+     if ((fd = open(path, O_WRONLY|O_CREAT|O_EXCL, 0666)) == -1) {
+          fprintf(stderr, "%s: error creating file %s: %s\n", __progname, path, strerror(errno));
+          return 1;
+     }
+     close(fd);
+     free(path);
+
+     asprintf(&path, "%s/immutable.f", dir);
+     if ((fd = open(path, O_WRONLY|O_CREAT|O_EXCL, 0666)) == -1) {
+          fprintf(stderr, "%s: error creating file %s: %s\n", __progname, path, strerror(errno));
+          return 1;
+     }
+     if (write(fd, immutable, strlen(immutable)) != strlen(immutable)) {
+         fprintf(stderr, "%s: error writing file %s: %s\n", __progname, path, strerror(errno));
+         return 1;
+     }
+     if (fadd_acl(fd, acl_u_text)) {
+         perror("acl");
+         return 1;
+     }
+     if (fsetxattr(fd, "trusted.test", "readonly", strlen("readonly"), XATTR_CREATE) != 0) {
+         if (errno != EOPNOTSUPP) {
+              perror("setxattr");
+              return 1;
+         }
+     }
+     if (fsetxattr(fd, "user.test", "readonly", strlen("readonly"), XATTR_CREATE) != 0) {
+         if (errno != EOPNOTSUPP) {
+              perror("setxattr");
+              return 1;
+         }
+     }
+     if (fsetflag(path, fd, 1, 1)) {
+          perror("fsetflag");
+          close(fd);
+          return 1;
+     }
+     close(fd); 
+     free(path);
+
+     asprintf(&path, "%s/append-only.f", dir);
+     if ((fd = open(path, O_WRONLY|O_CREAT|O_EXCL, 0666)) == -1) {
+          fprintf(stderr, "%s: error creating file %s: %s\n", __progname, path, strerror(errno));
+          return 1;
+     }
+     if (write(fd, append_only, strlen(append_only)) != strlen(append_only)) {
+          fprintf(stderr, "%s: error writing file %s: %s\n", __progname, path, strerror(errno));
+          return 1;
+     }
+     if (fadd_acl(fd, acl_u_text)) {
+          perror("acl");
+          return 1;
+     }
+     if (fsetxattr(fd, "trusted.test", "readonly", strlen("readonly"), XATTR_CREATE) != 0) {
+         if (errno != EOPNOTSUPP) {
+              perror("setxattr");
+              return 1;
+         }
+     }
+     if (fsetxattr(fd, "user.test", "readonly", strlen("readonly"), XATTR_CREATE) != 0) {
+         if (errno != EOPNOTSUPP) {
+              perror("setxattr");
+              return 1;
+         }
+     }
+     if (fsetflag(path, fd, 1, 0)) {
+          perror("fsetflag");
+          close(fd);
+          return 1;
+     }
+     close(fd);
+     free(path);
+
+     asprintf(&path, "%s/immutable.d", dir);
+     if ((fd = open(path, O_RDONLY)) == -1) {
+          fprintf(stderr, "%s: error opening %s: %s\n", __progname, path, strerror(errno));
+          return 1;
+     }
+     if (fadd_acl(fd, acl_u_text_d)) {
+          perror("acl");
+          return 1;
+     }
+     if (fsetxattr(fd, "trusted.test", "readonly", strlen("readonly"), XATTR_CREATE) != 0) {
+         if (errno != EOPNOTSUPP) {
+              perror("setxattr");
+              return 1;
+         }
+     }
+     if (fsetxattr(fd, "user.test", "readonly", strlen("readonly"), XATTR_CREATE) != 0) {
+         if (errno != EOPNOTSUPP) {
+              perror("setxattr");
+              return 1;
+         }
+     }
+     if (fsetflag(path, fd, 1, 1)) {
+          perror("fsetflag");
+          close(fd);
+          return 1;
+     }
+     close(fd);
+     free(path);
+
+     asprintf(&path, "%s/empty-immutable.d", dir); 
+     if ((fd = open(path, O_RDONLY)) == -1) {
+          fprintf(stderr, "%s: error opening %s: %s\n", __progname, path, strerror(errno));
+          return 1;
+     }
+     if (fsetflag(path, fd, 1, 1)) {
+          perror("fsetflag");
+          close(fd);
+          return 1;
+     }
+     close(fd); 
+     free(path);
+
+     asprintf(&path, "%s/append-only.d", dir); 
+     if ((fd = open(path, O_RDONLY)) == -1) {
+          fprintf(stderr, "%s: error opening %s: %s\n", __progname, path, strerror(errno));
+          return 1;
+     }
+     if (fadd_acl(fd, acl_u_text_d)) {
+          perror("acl");
+          return 1;
+     }
+     if (fsetxattr(fd, "trusted.test", "readonly", strlen("readonly"), XATTR_CREATE) != 0) {
+         if (errno != EOPNOTSUPP) {
+              perror("setxattr");
+              return 1;
+         }
+     }
+     if (fsetxattr(fd, "user.test", "readonly", strlen("readonly"), XATTR_CREATE) != 0) {
+         if (errno != EOPNOTSUPP) {
+              perror("setxattr");
+              return 1;
+         }
+     }
+     if (fsetflag(path, fd, 1, 0)) {
+          perror("fsetflag");
+          close(fd);
+          return 1;
+     }
+     close(fd); 
+     free(path);
+
+     asprintf(&path, "%s/empty-append-only.d", dir); 
+     if ((fd = open(path, O_RDONLY)) == -1) {
+          fprintf(stderr, "%s: error opening %s: %s\n", __progname, path, strerror(errno));
+          return 1;
+     }
+     if (fsetflag(path, fd, 1, 0)) {
+          perror("fsetflag");
+          close(fd);
+          return 1;
+     }
+     close(fd); 
+     free(path);
+     return 0;
+}
+
+static int remove_test_area(const char *dir)
+{
+     int fd;
+     int ret;
+     int err = 0;
+     char *path;
+     pid_t pid;
+     struct stat st;
+
+     if (getuid()) {
+         fprintf(stderr, "%s: you are not root, go away.\n", __progname);
+         return 1;
+     }
+
+     if (stat(dir, &st) == -1) {
+         fprintf(stderr, "%s: cannot remove test area %s: %s\n", __progname, dir, strerror(errno));
+         return 1;
+     }
+
+     asprintf(&path, "%s/immutable.d", dir);
+     if ((fd = open(path, O_RDONLY)) == -1) {
+          fprintf(stderr, "%s: error opening %s: %s\n", __progname, path, strerror(errno));
+         err = 1;
+     } else { 
+         if (fsetflag(path, fd, 0, 1))
+              perror("fsetflag");
+         close(fd);
+     }
+     free(path);
+
+     asprintf(&path, "%s/empty-immutable.d", dir); 
+     if ((fd = open(path, O_RDONLY)) == -1) {
+          fprintf(stderr, "%s: error opening %s: %s\n", __progname, path, strerror(errno));
+         err = 1;        
+     } else {
+         if (fsetflag(path, fd, 0, 1))
+              perror("fsetflag");
+     
+         close(fd);
+     }
+     free(path);
+
+     asprintf(&path, "%s/append-only.d", dir); 
+     if ((fd = open(path, O_RDONLY)) == -1) {
+          fprintf(stderr, "%s: error opening %s: %s\n", __progname, path, strerror(errno));
+         err = 1;
+     } else {
+         if (fsetflag(path, fd, 0, 0))
+              perror("fsetflag");
+         close(fd); 
+     }
+     free(path);
+
+     asprintf(&path, "%s/empty-append-only.d", dir); 
+     if ((fd = open(path, O_RDONLY)) == -1) {
+          fprintf(stderr, "%s: error opening %s: %s\n", __progname, path, strerror(errno));
+         err = 1;
+     } else {
+         if (fsetflag(path, fd, 0, 0))
+              perror("fsetflag");
+         close(fd); 
+     }
+     free(path);
+
+     asprintf(&path, "%s/append-only.f", dir);
+     if ((fd = open(path, O_RDONLY)) == -1) {
+          fprintf(stderr, "%s: error opening %s: %s\n", __progname, path, strerror(errno));
+         err = 1;
+     } else {
+         if (fsetflag(path, fd, 0, 0))
+              perror("fsetflag");
+
+         close(fd); 
+     }
+     free(path);
+
+     asprintf(&path, "%s/immutable.f", dir);
+     if ((fd = open(path, O_RDONLY)) == -1) {
+          fprintf(stderr, "%s: error opening %s: %s\n", __progname, path, strerror(errno));
+         err = 1;
+     } else {
+         if (fsetflag(path, fd, 0, 1))
+              perror("fsetflag");
+         close(fd); 
+     }
+     free(path);
+
+     if (err) {
+         fprintf(stderr, "%s: Warning, expected parts of the test area missing, not removing.\n", __progname);
+         return 1;
+     }
+
+     pid = fork();
+     if (!pid) {
+         execl("/bin/rm", "rm", "-rf", dir, NULL);
+         return 1;
+     } else if (pid == -1) {
+         perror("fork failed");
+         return 1;
+     }
+     wait(&ret);
+
+     return WEXITSTATUS(ret);
+}
+
+int main(int argc, char **argv)
+{
+     int ret;
+     int failed = 0;
+
+/* this arg parsing is gross, but who cares, its a test program */
+
+     if (argc < 2) {
+         fprintf(stderr, "usage: t_immutable [-C|-c|-r] test_area_dir\n");
+         return 1;
+     }
+
+     if (!strcmp(argv[1], "-c")) {
+         if (argc == 3) {
+              if ((ret = create_test_area(argv[argc-1])))
+                   return ret;
+         } else {
+              fprintf(stderr, "usage: t_immutable -c test_area_dir\n");
+              return 1;
+         }
+     } else if (!strcmp(argv[1], "-C")) {
+          if (argc == 3) {
+               return create_test_area(argv[argc-1]);
+          } else {
+               fprintf(stderr, "usage: t_immutable -C test_area_dir\n");
+               return 1;
+          }
+     } else if (!strcmp(argv[1], "-r")) {
+         if (argc == 3)
+              return remove_test_area(argv[argc-1]);
+         else {
+              fprintf(stderr, "usage: t_immutable -r test_area_dir\n");
+              return 1;
+         }
+     } else if (argc != 2) {
+         fprintf(stderr, "usage: t_immutable [-c|-r] test_area_dir\n");
+         return 1;
+     }
+
+     umask(0000);
+
+     if (check_test_area(argv[argc-1]))
+         return 1;
+
+     printf("testing immutable...");
+     if ((ret = test_immutable(argv[argc-1])) != 0) {
+         printf("FAILED! (%d tests failed)\n", ret);
+         failed = 1;
+     } else
+         puts("PASS.");
+
+     printf("testing append-only...");
+     if ((ret = test_append(argv[argc-1])) != 0) {
+          printf("FAILED! (%d tests failed)\n", ret);
+          failed = 1;
+     } else
+         puts("PASS.");
+
+     if (!getuid() && !failed) {
+         if (setgroups(0, NULL) != 0) 
+              perror("setgroups");
+         if (setgid(65534) != 0)
+              perror("setgid");
+         if (setuid(65534) != 0)
+              perror("setuid");
+         printf("testing immutable as non-root...");
+         if ((ret = test_immutable(argv[argc-1])) != 0) {
+              printf("FAILED! (%d tests failed)\n", ret);
+              failed = 1;
+         } else
+              puts("PASS.");
+         
+         printf("testing append-only as non-root...");
+         if ((ret = test_append(argv[argc-1])) != 0) {
+              printf("FAILED! (%d tests failed)\n", ret);
+              failed = 1;
+         } else
+              puts("PASS.");
+     }
+
+     return failed;
+}