// SPDX-License-Identifier: GPL-2.0+
/* 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
 */

#define TEST_UTIME

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#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 <sys/xattr.h>
#include <linux/fs.h>
#include <linux/magic.h>
#include <xfs/xfs.h>
#include <xfs/handle.h>
#include <xfs/jdm.h>

#ifndef XFS_SUPER_MAGIC
#define XFS_SUPER_MAGIC 0x58465342
#endif

extern const char *__progname;

static int fsetflag(const char *path, int fd, int on, int immutable)
{
#ifdef FS_IOC_SETFLAGS
     int fsflags = 0;
     int fsfl;

     if (ioctl(fd, FS_IOC_GETFLAGS, &fsflags) < 0) {
	  close(fd);
	  return 1;
     }
     if (immutable)
	  fsfl = FS_IMMUTABLE_FL;
     else
	  fsfl = FS_APPEND_FL;
     if (on)
	  fsflags |= fsfl;
     else
	  fsflags &= ~fsfl;
     if (ioctl(fd, FS_IOC_SETFLAGS, &fsflags) < 0) {
	  close(fd);
	  return 1;
     }
     close(fd);
     return 0;
#else
     errno = EOPNOTSUPP;
     close(fd);
     return 1;
#endif
}

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 && errno != EPERM) {
	  fprintf(stderr, "open(%s, O_RDWR) did not set errno == EACCES or 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 != EACCES && errno != EPERM) {
          fprintf(stderr, "open(%s, O_WRONLY) did not set errno == EACCES or 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 != EACCES && errno != EPERM) {
          fprintf(stderr, "open(%s, O_RDWR|O_TRUNC) did not set errno == EACCES or 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 != EACCES && errno != EPERM) {
          fprintf(stderr, "open(%s, O_WRONLY|O_TRUNC did not set errno == EACCES or EPERM\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 && errno != EPERM) {
          fprintf(stderr, "open(%s, O_RDWR|O_APPEND) did not set errno == EACCES or EPERM\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 && errno != EPERM) {
          fprintf(stderr, "open(%s, O_WRONLY|O_APPEND) did not set errno == EACCES or 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 != EACCES && errno != EPERM) {
          fprintf(stderr, "open(%s, O_RDWR|O_APPEND|O_TRUNC) did not set errno == EACCES or 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 != EACCES && errno != EPERM) {
          fprintf(stderr, "open(%s, O_WRONLY|O_APPEND|O_TRUNC) did not set errno == EACCES or EPERM\n", path);
          fail++;
     }

     if (stfs.f_type == XFS_SUPER_MAGIC && !getuid()) {
	  jdm_fshandle_t *fshandle;
	  struct xfs_bstat bstat;
	  struct xfs_fsop_bulkreq  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 = (__u64 *)&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 && errno != EPERM) {
	       perror("jdm_open");
	       fprintf(stderr, "jdm_open(%s, O_RDWR) did not set errno == EACCES or 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 != EACCES && errno != EPERM) {
	       fprintf(stderr, "jdm_open(%s, O_WRONLY) did not set errno == EACCES or 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 != EACCES && errno != EPERM) {
	       fprintf(stderr, "jdm_open(%s, O_RDWR|O_TRUNC) did not set errno == EACCES or 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 != EACCES && errno != EPERM) {
	       fprintf(stderr, "jdm_open(%s, O_WRONLY|O_TRUNC did not set errno == EACCES or EPERM\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 && errno != EPERM) {
	       fprintf(stderr, "jdm_open(%s, O_RDWR|O_APPEND) did not set errno == EACCES or EPERM\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 && errno != EPERM) {
	       fprintf(stderr, "jdm_open(%s, O_WRONLY|O_APPEND) did not set errno == EACCES or 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 != EACCES && errno != EPERM) {
	       fprintf(stderr, "jdm_open(%s, O_RDWR|O_APPEND|O_TRUNC) did not set errno == EACCES or 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 != EACCES && errno != EPERM) {
	       fprintf(stderr, "jdm_open(%s, O_WRONLY|O_APPEND|O_TRUNC) did not set errno == EACCES or 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 != EACCES && errno != EPERM) {
	  fprintf(stderr, "truncate(%s, 0) did not set errno == EACCES or 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) did not fail\n", path);
          fail++;
     } else if (errno != EACCES && errno != EPERM) {
          fprintf(stderr, "utime(%s, NULL) did not set errno == EACCES or EPERM\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, (int)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 && errno != EPERM) {
	  fprintf(stderr, "link(%s, %s) did not set errno == EACCES or EPERM\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 && errno != EPERM) {
	  fprintf(stderr, "symlink(%s, %s) did not set errno == EACCES or EPERM\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 && errno != EPERM) {
	  fprintf(stderr, "rename(%s, %s) did not set errno == EACCES or EPERM\n", path, linkpath);
          fail++;
     }
     free(linkpath);

     if (unlink(path) != -1) {
	  fprintf(stderr, "unlink(%s) did not fail\n", path);
	  fail++;
     } else if (errno != EACCES && errno != EPERM) {
	  fprintf(stderr, "unlink(%s) did not set errno == EACCES or EPERM\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 && errno != EPERM) {
	  fprintf(stderr, "open(%s, O_RDWR|O_CREAT, 0666) did not set errno == EACCES or EPERM\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, (long long int)st.st_rdev);
		    fail++;
		    unlink(path);
	       } else if (errno != EACCES && errno != EPERM) {
		    fprintf(stderr, "mknod(%s, S_IFCHR|0666, %lld) did not set errno == EACCES or EPERM\n", path, (long long int)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 && errno != EPERM) {
	  fprintf(stderr, "mkdir(%s, 0777) did not set errno == EACCES or EPERM\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, (int)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 && errno != EPERM) {
	  fprintf(stderr, "rmdir(%s) did not set errno == EACCES or EPERM\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 && errno != EPERM) {
          fprintf(stderr, "utime(%s, NULL) did not set errno == EACCES or EPERM\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 ((fd = open(path, O_RDWR|O_APPEND)) == -1) {
	  fprintf(stderr, "open(%s, O_RDWR|O_APPEND) failed: %s\n", path, strerror(errno));
	  fail++;
     } else {
	     if (ftruncate(fd, 0) != -1) {
		     fprintf(stderr, "ftruncate(%s, 0) did not fail\n", path);
		     fail++;
	     } else if (errno != EPERM) {
		     fprintf(stderr, "ftruncate(%s, 0) did not set errno == EPERM\n", path);
		     fail++;
	     }
	     close(fd);
     }

     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;
	  struct xfs_bstat bstat;
	  struct xfs_fsop_bulkreq  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 = (__u64 *)&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, (int)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, (int)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,
				      (int)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, (int)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, (int)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,
				      (int)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;
          struct xfs_bstat bstat;
          struct xfs_fsop_bulkreq  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 = (__u64 *)&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, (int)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, (int)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,
					   (int)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, (int)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, (int)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,
					   (int)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, (int)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, (long long int)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, (int)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, (int)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 allow_existing;

static int create_dir(char **ppath, const char *fmt, const char *dir)
{
     const char *path;
     struct stat st;

     if (asprintf(ppath, fmt, dir) == -1) {
	  return 1;
     }
     path = *ppath;
     if (stat(path, &st) == 0) {
	  if (allow_existing && S_ISDIR(st.st_mode)) {
	       return 0;
	  }
	  fprintf(stderr, "%s: Test area directory %s must not exist for test area creation.\n",
		  __progname, path);
	  return 1;
     }
     if (mkdir(path, 0777) != 0) {
	  fprintf(stderr, "%s: error creating directory %s: %s\n", __progname, path, strerror(errno));
	  return 1;
     }
     return 0;
}

static int create_file(char **ppath, const char *fmt, const char *dir)
{
     int flags = O_WRONLY|O_CREAT | (allow_existing ? 0 : O_EXCL);
     const char *path;
     int fd;

     if (asprintf(ppath, fmt, dir) == -1) {
	  return 1;
     }
     path = *ppath;
     if ((fd = open(path, flags, 0666)) == -1) {
	  fprintf(stderr, "%s: error creating file %s: %s\n", __progname, path, strerror(errno));
          return 1;
     }
     return fd;
}

static int create_xattrs(int fd)
{
     int flags = allow_existing ? 0 : XATTR_CREATE;

     if (fsetxattr(fd, "trusted.test", "readonly", strlen("readonly"), flags) != 0) {
	  if (errno != EOPNOTSUPP) {
	       perror("setxattr");
	       return 1;
	  }
     }
     if (fsetxattr(fd, "user.test", "readonly", strlen("readonly"), flags) != 0) {
	  if (errno != EOPNOTSUPP) {
	       perror("setxattr");
	       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";
     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;
     }

     umask(0000);
     if (create_dir(&path, "%s", dir)) {
	  return 1;
     }
     free(path);

     if (create_dir(&path, "%s/append-only.d", dir)) {
	  return 1;
     }
     free(path);

     if (create_dir(&path, "%s/append-only.d/dir", dir)) {
	  return 1;
     }
     free(path);

     if ((fd = create_file(&path, "%s/append-only.d/file", dir)) == -1) {
          return 1;
     }
     close(fd);
     free(path);

     if (create_dir(&path, "%s/immutable.d", dir)) {
	  return 1;
     }
     free(path);

     if (create_dir(&path, "%s/immutable.d/dir", dir)) {
	  return 1;
     }
     free(path);

     if ((fd = create_file(&path, "%s/immutable.d/file", dir)) == -1) {
          return 1;
     }
     close(fd);
     free(path);

     if ((fd = create_file(&path, "%s/immutable.f", dir)) == -1) {
          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 (create_xattrs(fd)) {
	  return 1;
     }
     if (fsetflag(path, fd, 1, 1)) {
          perror("fsetflag");
          close(fd);
          return 1;
     }
     close(fd);
     free(path);

     if ((fd = create_file(&path, "%s/append-only.f", dir)) == -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 (create_xattrs(fd)) {
	  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 (create_xattrs(fd)) {
	  return 1;
     }
     if (fsetflag(path, fd, 1, 1)) {
          perror("fsetflag");
          close(fd);
          return 1;
     }
     close(fd);
     free(path);

     if (create_dir(&path, "%s/empty-immutable.d", dir)) {
	  return 1;
     }
     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 (create_xattrs(fd)) {
	  return 1;
     }
     if (fsetflag(path, fd, 1, 0)) {
          perror("fsetflag");
          close(fd);
          return 1;
     }
     close(fd);
     free(path);

     if (create_dir(&path, "%s/empty-append-only.d", dir)) {
	  return 1;
     }
     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;
     }

     if (allow_existing) {
	     return 0;
     }

     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;
     int runtest = 1, create = 0, remove = 0;

/* this arg parsing is gross, but who cares, its a test program */

     if (argc < 2) {
	  fprintf(stderr, "usage: t_immutable [-C|-c|-R|-r] test_area_dir\n");
	  return 1;
     }

     if (!strcmp(argv[1], "-c")) {
	  create = 1;
     } else if (!strcmp(argv[1], "-C")) {
	  /* Prepare test area without running tests */
	  create = 1;
	  runtest = 0;
	  /* With existing test area, only setflags */
	  allow_existing = 1;
     } else if (!strcmp(argv[1], "-r")) {
	  remove = 1;
     } else if (!strcmp(argv[1], "-R")) {
	  /* Cleanup flags on test area but leave the files */
	  remove = 1;
	  allow_existing = 1;
     }

     if (argc != 2 + (create | remove)) {
	  fprintf(stderr, "usage: t_immutable [-C|-c|-R|-r] test_area_dir\n");
	  return 1;
     }

     if (create) {
	  ret = create_test_area(argv[argc-1]);
	  if (ret || allow_existing || !runtest) {
               return ret;
	  }
     } else if (remove) {
	  return remove_test_area(argv[argc-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;
}
