--- /dev/null
+#! /bin/sh
+# XFS QA Test No. 089
+#
+# Emulate the way Linux mount manipulates /etc/mtab to attempt to
+# reproduce a possible bug in rename (see src/t_mtab.c).
+#
+#-----------------------------------------------------------------------
+# Copyright (c) 2000-2004 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`
+tmp=/tmp/$$
+status=1 # failure is the default!
+trap "rm -f $tmp.*; exit \$status" 0 1 2 3 15
+
+# get standard environment, filters and checks
+. ./common.rc
+. ./common.filter
+
+# real QA test starts here
+[ "X$TEST_DIR" = "X" ] && exit 1
+cd $TEST_DIR
+rm -fr test
+mkdir test || exit 1
+cd $TEST_DIR/test
+mount > t_mtab
+
+$here/src/t_mtab 2 &
+$here/src/t_mtab 2 &
+$here/src/t_mtab 2 &
+wait
+
+$here/src/t_mtab 10000
+
+echo directory entries:
+ls
+ls -li > $seq.full
+
+# success, all done
+status=0
+exit
--- /dev/null
+/*
+ * Test program based on Linux mount(8) source attempting to
+ * trigger a suspected problem in rename(2) code paths - its
+ * symptoms have been multiple mtab entries in /etc... hence
+ * use of the actual mount code here.
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <mntent.h>
+#include <limits.h>
+
+#define LOCK_TIMEOUT 10
+#define _(x) (x)
+
+static char *mounted = "t_mtab";
+static char *mounted_lock = "t_mtab~";
+static char *mounted_temp = "t_mtab.tmp";
+
+/* Updating mtab ----------------------------------------------*/
+
+/* Flag for already existing lock file. */
+static int we_created_lockfile = 0;
+
+/* Flag to indicate that signals have been set up. */
+static int signals_have_been_setup = 0;
+
+/* Ensure that the lock is released if we are interrupted. */
+static void
+handler (int sig) {
+ fprintf(stderr, "%s", sys_siglist[sig]);
+ exit(1);
+}
+
+static void
+setlkw_timeout (int sig) {
+ /* nothing, fcntl will fail anyway */
+}
+
+/* Create the lock file.
+ The lock file will be removed if we catch a signal or when we exit. */
+/* The old code here used flock on a lock file /etc/mtab~ and deleted
+ this lock file afterwards. However, as rgooch remarks, that has a
+ race: a second mount may be waiting on the lock and proceed as
+ soon as the lock file is deleted by the first mount, and immediately
+ afterwards a third mount comes, creates a new /etc/mtab~, applies
+ flock to that, and also proceeds, so that the second and third mount
+ now both are scribbling in /etc/mtab.
+ The new code uses a link() instead of a creat(), where we proceed
+ only if it was us that created the lock, and hence we always have
+ to delete the lock afterwards. Now the use of flock() is in principle
+ superfluous, but avoids an arbitrary sleep(). */
+
+void
+lock_mtab (void) {
+ int tries = 3;
+ char linktargetfile[PATH_MAX + 20];
+
+ if (!signals_have_been_setup) {
+ int sig = 0;
+ struct sigaction sa;
+
+ sa.sa_handler = handler;
+ sa.sa_flags = 0;
+ sigfillset (&sa.sa_mask);
+
+ while (sigismember (&sa.sa_mask, ++sig) != -1
+ && sig != SIGCHLD) {
+ if (sig == SIGALRM)
+ sa.sa_handler = setlkw_timeout;
+ else
+ sa.sa_handler = handler;
+ sigaction (sig, &sa, (struct sigaction *) 0);
+ }
+ signals_have_been_setup = 1;
+ }
+
+ /* use 20 as upper bound for the length of %d output */
+ snprintf(linktargetfile, PATH_MAX+20, "%s%d", mounted_lock, getpid());
+
+ /* Repeat until it was us who made the link */
+ while (!we_created_lockfile) {
+ struct flock flock;
+ int fd, errsv, i, j;
+
+ i = open (linktargetfile, O_WRONLY|O_CREAT, 0);
+ if (i < 0) {
+ int errsv = errno;
+ /* linktargetfile does not exist (as a file)
+ and we cannot create it. Read-only filesystem?
+ Too many files open in the system?
+ Filesystem full? */
+ fprintf(stderr, "can't create lock file %s: %s "
+ "(use -n flag to override)",
+ linktargetfile, strerror (errsv));
+ exit(1);
+ }
+ close(i);
+
+ j = link(linktargetfile, mounted_lock);
+ errsv = errno;
+
+ (void) unlink(linktargetfile);
+
+ if (j < 0 && errsv != EEXIST) {
+ fprintf(stderr, "can't link lock file %s: %s "
+ "(use -n flag to override)",
+ mounted_lock, strerror (errsv));
+ exit(1);
+ }
+
+ fd = open (mounted_lock, O_WRONLY);
+
+ if (fd < 0) {
+ int errsv = errno;
+ /* Strange... Maybe the file was just deleted? */
+ if (errno == ENOENT && tries-- > 0)
+ continue;
+ fprintf(stderr, "can't open lock file %s: %s "
+ "(use -n flag to override)",
+ mounted_lock, strerror (errsv));
+ exit(1);
+ }
+
+ flock.l_type = F_WRLCK;
+ flock.l_whence = SEEK_SET;
+ flock.l_start = 0;
+ flock.l_len = 0;
+
+ if (j == 0) {
+ /* We made the link. Now claim the lock. */
+ if (fcntl (fd, F_SETLK, &flock) == -1) {
+ int errsv = errno;
+ printf(_("Can't lock lock file %s: %s\n"),
+ mounted_lock, strerror (errsv));
+ /* proceed anyway */
+ }
+ we_created_lockfile = 1;
+ } else {
+ static int tries = 0;
+
+ /* Someone else made the link. Wait. */
+ alarm(LOCK_TIMEOUT);
+ if (fcntl (fd, F_SETLKW, &flock) == -1) {
+ int errsv = errno;
+ fprintf(stderr, "can't lock lock file %s: %s",
+ mounted_lock, (errno == EINTR) ?
+ _("timed out") : strerror (errsv));
+ exit(1);
+ }
+ alarm(0);
+ /* Limit the number of iterations - maybe there
+ still is some old /etc/mtab~ */
+ if (tries++ > 3) {
+ if (tries > 5) {
+ fprintf(stderr, "Cant create link %s\n"
+ "Perhaps there is a stale lock file?\n",
+ mounted_lock);
+ exit(1);
+ }
+ sleep(1);
+ }
+ }
+ close (fd);
+ }
+}
+
+/* Remove lock file. */
+void
+unlock_mtab (void) {
+ if (we_created_lockfile) {
+ unlink (mounted_lock);
+ we_created_lockfile = 0;
+ }
+}
+
+/*
+ * Update the mtab.
+ * Used by umount with null INSTEAD: remove the last DIR entry.
+ * Used by mount upon a remount: update option part,
+ * and complain if a wrong device or type was given.
+ * [Note that often a remount will be a rw remount of /
+ * where there was no entry before, and we'll have to believe
+ * the values given in INSTEAD.]
+ */
+
+void
+update_mtab (void)
+{
+ FILE *mntent_fp, *mftmp;
+ char buffer[4096];
+ int size;
+
+ lock_mtab();
+
+ /* having locked mtab, read it again & write to mtemp */
+ mntent_fp = fopen(mounted, "r");
+ if (!mntent_fp) {
+ fprintf(stderr, "cannot open %s for reading\n", mounted);
+ exit(1);
+ }
+ mftmp = fopen(mounted_temp, "w");
+ if (!mftmp) {
+ fprintf(stderr, "cannot open %s for writing\n", mounted_temp);
+ exit(1);
+ }
+ while ((size = read(fileno(mntent_fp), buffer, sizeof(buffer))) > 0) {
+ if (write(fileno(mftmp), buffer, size) < 0) {
+ fprintf(stderr, "write failure: %s\n", strerror(errno));
+ exit(1);
+ }
+ }
+ if (size < 0) {
+ fprintf(stderr, "read failure: %s\n", strerror(errno));
+ exit(1);
+ }
+ fclose(mntent_fp);
+
+ if (fchmod (fileno (mftmp),
+ S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) < 0) {
+ int errsv = errno;
+ fprintf(stderr, _("error changing mode of %s: %s\n"),
+ mounted_temp, strerror (errsv));
+ }
+ fclose(mftmp);
+
+ { /*
+ * If mount is setuid and some non-root user mounts sth,
+ * then mtab.tmp might get the group of this user. Copy uid/gid
+ * from the present mtab before renaming.
+ */
+ struct stat sbuf;
+ if (stat (mounted, &sbuf) == 0)
+ chown (mounted_temp, sbuf.st_uid, sbuf.st_gid);
+ }
+
+ /* rename mtemp to mtab */
+ if (rename (mounted_temp, mounted) < 0) {
+ int errsv = errno;
+ fprintf(stderr, _("can't rename %s to %s: %s\n"),
+ mounted_temp, mounted, strerror(errsv));
+ }
+
+ unlock_mtab();
+}
+
+int main(int argc, char **argv)
+{
+ int i, stop = 100000;
+
+ if (argc > 1)
+ stop = atoi(argv[1]);
+
+ for (i = 0; i < stop; i++) {
+ update_mtab();
+ }
+ printf("completed %d iterations\n", stop);
+ return 0;
+}