| /* |
| * 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 120 |
| #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\n", strsignal(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) { |
| #if 0 /* nathans: dont limit, we are forcing lots of parallel accesses */ |
| int tries = 3; |
| #endif |
| 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)\n", |
| 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)\n", |
| 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 0 /* nathans: dont limit, we are forcing lots of parallel accesses */ |
| if (errno == ENOENT && tries-- > 0) |
| #endif |
| if (errno == ENOENT) |
| continue; |
| fprintf(stderr, "can't open lock file %s: %s\n", |
| 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 && |
| errno != EBUSY && errno != EAGAIN) { |
| int errsv = errno; |
| printf(_("Can't lock lock file %s: %s\n"), |
| mounted_lock, strerror (errsv)); |
| /* proceed anyway */ |
| } |
| we_created_lockfile = 1; |
| } else { |
| #if 0 /* nathans: dont limit, we are forcing lots of parallel accesses */ |
| static int tries = 0; |
| #endif |
| |
| /* Someone else made the link. Wait. */ |
| alarm(LOCK_TIMEOUT); |
| if (fcntl (fd, F_SETLKW, &flock) == -1 && |
| errno != EBUSY && errno != EAGAIN) { |
| int errsv = errno; |
| fprintf(stderr, "can't lock lock file %s: %s\n", |
| mounted_lock, (errno == EINTR) ? |
| _("timed out") : strerror (errsv)); |
| exit(1); |
| } |
| alarm(0); |
| #if 0 /* nathans: dont limit, we are forcing lots of parallel accesses */ |
| /* 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); |
| } |
| #endif |
| } |
| close (fd); |
| } |
| } |
| |
| /* Remove lock file. */ |
| void |
| unlock_mtab (void) { |
| int ret; |
| if (we_created_lockfile) { |
| ret = unlink (mounted_lock); |
| if (ret) { |
| fprintf(stderr, "Cannot remove lock file: %s\n", strerror(errno)); |
| exit(1); |
| } else { |
| 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; |
| FILE *fout = NULL; |
| |
| if (argc > 1) |
| stop = atoi(argv[1]); |
| |
| for (i = 0; i < stop; i++) { |
| update_mtab(); |
| } |
| |
| if (argc > 2) |
| fout = fopen(argv[2],"a"); |
| if (!fout) |
| fout = stdout; |
| fprintf(fout, "completed %d iterations\n", stop); |
| return 0; |
| } |