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>