Date: Tue, 25 Apr 95 18:15:37 EST From: Stephen McKay <syssgm@devetir.qld.gov.au> To: current@FreeBSD.org Cc: syssgm@devetir.qld.gov.au Subject: fcntl F_SETLK backward compatibility hack implemented Message-ID: <199504250815.SAA15397@pandora.devetir.qld.gov.au>
next in thread | raw e-mail | index | archive | help
Well, since nobody objected :-) I went and implemented my proposed fix to
fcntl locking. Here are patches to sys/fcntl.h and kern/kern_descrip.c
plus two test programs. The 'break' program demonstrates how the scheme
can fail, and allows you to look at the pretty console warnings.
The ftest program sets a few random locks and checks for clashes. If any
console messages turn up, it has failed. I compile it on a 1.1.5 system
and a 2.0 system then run it like:
$ ./ftest.1.1.5 & ./ftest.2.0 ; sleep 3
048901919: 374: OK F_WRLCK SEEK_CUR -3722 6989 == 2201 to 9189
048937427: 375: NO F_WRLCK SEEK_SET 6048 3952 == 6048 to 9999
048940850: 375: -> F_WRLCK SEEK_SET 2201 6989 == 2201 to 9189 PID:374
050970752: 375: OK F_WRLCK SEEK_SET 6048 3952 == 6048 to 9999
051003186: 374: OK F_WRLCK SEEK_CUR 33 1554 == 4191 to 5744
...
This shows a microsecond timestamp, the pid, success/failure/reason, and
the expanded flock structure.
I think the patch works well, and since it changes the real fcntl lock code
so little, it won't make things any worse!
Stephen.
# This is a shell archive. Save it in a file, remove anything before
# this line, and then unpack it by entering "sh file". Note, it may
# create directories; files and directories will be owned by you and
# have default permissions.
#
# This archive contains:
#
# diffs
# Makefile
# ftest.c
# break.c
#
echo x - diffs
sed 's/^X//' >diffs << 'END-of-diffs'
X--- sys/fcntl.h.dist Tue Aug 2 17:52:57 1994
X+++ sys/fcntl.h Sun Apr 23 22:13:31 1995
X@@ -135,10 +135,13 @@
X #ifndef _POSIX_SOURCE
X #define F_GETOWN 5 /* get SIGIO/SIGURG proc/pgrp */
X #define F_SETOWN 6 /* set SIGIO/SIGURG proc/pgrp */
X+#define F_OGETLK 7 /* 4.3 backward compatible GETLK */
X+#define F_OSETLK 8 /* 4.3 backward compatible SETLK */
X+#define F_OSETLKW 9 /* 4.3 backward compatible SETLKW */
X #endif
X-#define F_GETLK 7 /* get record locking information */
X-#define F_SETLK 8 /* set record locking information */
X-#define F_SETLKW 9 /* F_SETLK; wait if blocked */
X+#define F_GETLK 10 /* get record locking information */
X+#define F_SETLK 11 /* set record locking information */
X+#define F_SETLKW 12 /* F_SETLK; wait if blocked */
X
X /* file descriptor flags (F_GETFD, F_SETFD) */
X #define FD_CLOEXEC 1 /* close-on-exec flag */
X@@ -164,6 +167,19 @@
X short l_type; /* lock type: read/write, etc. */
X short l_whence; /* type of l_start */
X };
X+
X+#ifndef _POSIX_SOURCE
X+/*
X+ * 4.3 backward compatible advisory lock structure.
X+ */
X+struct oflock {
X+ short ol_type; /* lock type: read/write, etc. */
X+ short ol_whence; /* type of l_start */
X+ long ol_start; /* starting offset */
X+ long ol_len; /* len = 0 means until end of file */
X+ short ol_pid; /* lock owner */
X+};
X+#endif
X
X
X #ifndef _POSIX_SOURCE
X--- kern/kern_descrip.c.dist Mon Oct 3 03:35:11 1994
X+++ kern/kern_descrip.c Tue Apr 25 18:07:52 1995
X@@ -263,6 +263,7 @@
X error = copyin((caddr_t)uap->arg, (caddr_t)&fl, sizeof (fl));
X if (error)
X return (error);
X+ f_setlk:
X if (fl.l_whence == SEEK_CUR)
X fl.l_start += fp->f_offset;
X switch (fl.l_type) {
X@@ -295,11 +296,138 @@
X error = copyin((caddr_t)uap->arg, (caddr_t)&fl, sizeof (fl));
X if (error)
X return (error);
X+ f_getlk:
X if (fl.l_whence == SEEK_CUR)
X fl.l_start += fp->f_offset;
X if ((error = VOP_ADVLOCK(vp,(caddr_t)p,F_GETLK,&fl,F_POSIX)))
X return (error);
X return (copyout((caddr_t)&fl, (caddr_t)uap->arg, sizeof (fl)));
X+
X+#if defined(COMPAT_43) || defined(COMPAT_SUNOS)
X+
X+#define VALID_L_TYPE(t) ((t)==F_RDLCK || (t)==F_UNLCK || (t)==F_WRLCK)
X+#define VALID_WHENCE(w) ((w)==SEEK_SET || (w)==SEEK_CUR || (w)==SEEK_END)
X+
X+ /*
X+ * Backward compatible fcntl lock calls weren't added when the
X+ * flock structure was changed. We have to use some guesswork
X+ * to make up for this omission.
X+ *
X+ * Code by: Stephen McKay <syssgm@devetir.qld.gov.au>
X+ */
X+ case F_OSETLKW:
X+ case F_OSETLK:
X+ case F_OGETLK: {
X+ int is_new = 1, is_old = 1;
X+ long start_high, len_high;
X+ struct oflock ofl;
X+
X+ if (fp->f_type != DTYPE_VNODE)
X+ return (EBADF);
X+ vp = (struct vnode *)fp->f_data;
X+
X+ /*
X+ * Copy in the lock structure, being ultra cautious in case
X+ * it is an old style structure at the end of memory.
X+ */
X+ error = copyin((caddr_t)uap->arg, &fl, sizeof (fl));
X+ if (error) {
X+ error = copyin((caddr_t)uap->arg, &ofl, sizeof (ofl));
X+ if (error)
X+ return (error);
X+ is_new = 0;
X+ } else
X+ bcopy(&fl, &ofl, sizeof (ofl));
X+
X+ /*
X+ * Since 1.1.5 and 2.0 binaries both use the old lock calls
X+ * but with different flock structures, we have to guess
X+ * which one we have been passed by using whatever consistency
X+ * checks are available.
X+ */
X+ if (!VALID_L_TYPE(fl.l_type) || !VALID_WHENCE(fl.l_whence))
X+ is_new = 0;
X+
X+ if (!VALID_L_TYPE(ofl.ol_type) || !VALID_WHENCE(ofl.ol_whence))
X+ is_old = 0;
X+
X+ if (fl.l_len < 0 || fl.l_whence == SEEK_SET && fl.l_start < 0)
X+ is_new = 0;
X+
X+ if (ofl.ol_len<0 || ofl.ol_whence==SEEK_SET && ofl.ol_start<0)
X+ is_old = 0;
X+
X+ if (fl.l_whence == SEEK_CUR && fl.l_start + fp->f_offset < 0)
X+ is_new = 0;
X+
X+ if (ofl.ol_whence==SEEK_CUR && ofl.ol_start + fp->f_offset < 0)
X+ is_old = 0;
X+
X+ start_high = fl.l_start >> 32;
X+ len_high = fl.l_len >> 32;
X+ if (start_high != 0 && start_high != -1 || len_high != 0)
X+ is_new = 0;
X+
X+ /*
X+ * If we can't be sure, assume it is 1.1.5 or older, and
X+ * be a little noisy about it, in case we are wrong.
X+ *
X+ * XXXX It may be even better to return an error here and
X+ * make no uncertain guesses.
X+ */
X+ if (is_old == is_new) {
X+ printf("pid %lu assuming old fcntl lock structure\n",
X+ (u_long)curproc->p_pid);
X+ is_new = 0;
X+ is_old = 1;
X+ }
X+
X+ if (is_old) {
X+ fl.l_type = ofl.ol_type;
X+ fl.l_whence = ofl.ol_whence;
X+ fl.l_start = ofl.ol_start;
X+ fl.l_len = ofl.ol_len;
X+ fl.l_pid = ofl.ol_pid;
X+ }
X+
X+ /*
X+ * Now that we have the data in a new style flock structure
X+ * most cases can be handled by jumping back into the normal
X+ * locking code. F_GETLK on an old style lock is the only
X+ * exception.
X+ */
X+ switch (uap->cmd) {
X+ case F_OSETLKW:
X+ flg |= F_WAIT;
X+ /* Fall into F_OSETLK */
X+
X+ case F_OSETLK:
X+ goto f_setlk;
X+
X+ case F_OGETLK:
X+ if (is_new)
X+ goto f_getlk;
X+
X+ if (fl.l_whence == SEEK_CUR)
X+ fl.l_start += fp->f_offset;
X+ error = VOP_ADVLOCK(vp,(caddr_t)p,F_GETLK,&fl,F_POSIX);
X+ if (error)
X+ return (error);
X+
X+ ofl.ol_type = fl.l_type;
X+ ofl.ol_whence = fl.l_whence;
X+ ofl.ol_start = (long)fl.l_start;
X+ ofl.ol_len = (long)fl.l_len;
X+ ofl.ol_pid = (short)fl.l_pid;
X+
X+ return (copyout(&ofl, (caddr_t)uap->arg, sizeof (ofl)));
X+ }
X+ }
X+
X+#undef VALID_L_TYPE
X+#undef VALID_WHENCE
X+
X+#endif /* defined(COMPAT_43) || defined(COMPAT_SUNOS) */
X
X default:
X return (EINVAL);
END-of-diffs
echo x - Makefile
sed 's/^X//' >Makefile << 'END-of-Makefile'
XCFLAGS= -O2 -Wall
X
Xall: ftest break
X
Xftest: ftest.o
X $(CC) -o ftest ftest.o
X
Xbreak: break.o
X $(CC) -o break break.o
END-of-Makefile
echo x - ftest.c
sed 's/^X//' >ftest.c << 'END-of-ftest.c'
X/*
X * Simple tests to determine if the backward compatible fcntl lock
X * mechanism works.
X */
X
X#include <stdio.h>
X#include <stdlib.h>
X#include <unistd.h>
X#include <fcntl.h>
X#include <errno.h>
X#include <sys/time.h>
X
X#define FILE_SIZE 8192
X
Xchar junk[FILE_SIZE];
X
Xvoid lock_rnd(int fd);
Xvoid try_lock(int fd, struct flock *fl);
Xvoid unlock(int fd, struct flock *fl);
Xvoid log(char *msg, struct flock *fl, int do_pid);
X
X#define FALSE 0
X#define TRUE 1
X
X#define rnd(n) (random()%(n))
X
Xoff_t where = 0;;
X
Xint
Xmain(argc, argv)
Xint argc;
Xchar **argv;
X {
X int fd;
X int i;
X char *name = "TOAD";
X
X srandom(getpid());
X
X if ((fd = open(name, O_RDWR|O_CREAT|O_TRUNC, 0600)) == -1)
X perror(name), exit(1);
X
X write(fd, junk, sizeof(junk));
X lseek(fd, where, SEEK_SET);
X
X for (i = 0; i < 5; i++)
X lock_rnd(fd);
X
X return 0;
X }
X
X
Xvoid
Xlock_rnd(int fd)
X {
X struct flock fl;
X
X fl.l_type = F_WRLCK;
X switch (rnd(3))
X {
X case 0:
X fl.l_whence = SEEK_SET;
X fl.l_start = rnd(FILE_SIZE);
X fl.l_len = rnd(FILE_SIZE);
X break;
X
X case 1:
X fl.l_whence = SEEK_CUR;
X where = lseek(fd, (off_t)rnd(FILE_SIZE), SEEK_SET);
X if (where < 0)
X {
X perror("lseek");
X exit(1);
X }
X fl.l_start = rnd(FILE_SIZE) - where;
X fl.l_len = rnd(FILE_SIZE);
X break;
X
X case 2:
X fl.l_whence = SEEK_END;
X fl.l_start = rnd(FILE_SIZE) - FILE_SIZE;
X fl.l_len = rnd(FILE_SIZE);
X break;
X }
X
X try_lock(fd, &fl);
X sleep(2);
X unlock(fd, &fl);
X }
X
X#define MAGIC1 0x9174e2fa
X#define MAGIC2 0x5d738fe2
X
Xvoid
Xtry_lock(int fd, struct flock *fl)
X {
X int set = F_SETLK;
X volatile unsigned flank1 = MAGIC1;
X volatile struct flock blocker;
X volatile unsigned flank2 = MAGIC2;
X
X for (;;)
X {
X if (fcntl(fd, set, fl) == 0)
X {
X log("OK", fl, FALSE);
X return;
X }
X if (errno != EWOULDBLOCK)
X {
X perror("SETLK");
X exit(1);
X }
X log("NO", fl, FALSE);
X blocker = *fl;
X if (fcntl(fd, F_GETLK, &blocker) < 0)
X {
X perror("GETLK");
X exit(1);
X }
X log("->", (struct flock *)&blocker, TRUE);
X if (flank1 != MAGIC1)
X {
X printf("GETLK overwrote flank1\n");
X exit(1);
X }
X if (flank2 != MAGIC2)
X {
X printf("GETLK overwrote flank2\n");
X exit(1);
X }
X set = F_SETLKW;
X }
X }
X
X
Xvoid
Xunlock(int fd, struct flock *fl)
X {
X struct flock x;
X
X x = *fl;
X x.l_type = F_UNLCK;
X if (fcntl(fd, F_SETLK, &x) < 0)
X {
X perror("UNLCK");
X exit(1);
X }
X }
X
X
Xchar *lock_names[] =
X {
X "", "F_RDLCK", "F_UNLCK", "F_WRLCK"
X };
X
Xchar *seek_names[] =
X {
X "SEEK_SET", "SEEK_CUR", "SEEK_END"
X };
X
Xvoid
Xlog(char *msg, struct flock *fl, int do_pid)
X {
X off_t start;
X struct timeval tv;
X
X gettimeofday(&tv, NULL);
X
X printf("%09ld: ", (tv.tv_sec % 1000) * 1000000 + tv.tv_usec);
X printf("%ld: %s", (long)getpid(), msg);
X printf(" %s %s", lock_names[fl->l_type], seek_names[fl->l_whence]);
X printf(" %5ld %5ld", (long)fl->l_start, (long)fl->l_len);
X start = fl->l_start;
X switch (fl->l_whence)
X {
X case SEEK_CUR:
X start += where;
X break;
X case SEEK_END:
X start += FILE_SIZE;
X break;
X }
X printf(" == %5ld to %5ld", (long)start, (long)(start+fl->l_len-1));
X if (do_pid)
X printf(" PID:%ld", (long)fl->l_pid);
X printf("\n");
X }
END-of-ftest.c
echo x - break.c
sed 's/^X//' >break.c << 'END-of-break.c'
X/*
X * A test of the cases where the backward compatible fcntl locking
X * doesn't work.
X */
X
X#include <stdio.h>
X#include <stdlib.h>
X#include <unistd.h>
X#include <fcntl.h>
X#include <string.h>
X
Xint
Xmain(argc, argv)
Xint argc;
Xchar **argv;
X {
X int fd;
X char *name = "TOAD";
X struct
X {
X struct flock fl;
X int pid;
X short type;
X short whence;
X } it;
X
X if ((fd = open(name, O_RDWR|O_CREAT|O_TRUNC, 0600)) == -1)
X perror(name), exit(1);
X
X /*
X * This lock is ambiguous on a 2.0 system and will default to "old".
X */
X it.fl.l_type = F_WRLCK;
X it.fl.l_whence = SEEK_SET;
X it.fl.l_start = 0x00010001;
X it.fl.l_len = 0;
X if (fcntl(fd, F_SETLK, &it.fl) < 0)
X perror("set1");
X
X /*
X * This lock is ambiguous on a 1.1.5 system and will default to "old".
X * Note that without the bzero(), the uninitialised areas usually
X * remove the ambiguity.
X */
X bzero(&it, sizeof(it));
X it.fl.l_type = F_WRLCK;
X it.fl.l_whence = SEEK_SET;
X it.fl.l_start = 0;
X it.fl.l_len = 0;
X it.type = F_RDLCK;
X it.whence = SEEK_CUR;
X if (fcntl(fd, F_SETLK, &it.fl) < 0)
X perror("set2");
X
X return 0;
X }
END-of-break.c
exit
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?199504250815.SAA15397>
