# This is a shell archive. # Remove everything above and including the cut line. # Then run the rest of the file through sh. #----cut here-----cut here-----cut here-----cut here----# #!/bin/sh # shar: Shell Archiver # Run the following text with /bin/sh to create: # README # fcntl.patch # This archive created: Tue Aug 18 14:16:48 1992 cat << \SHAR_EOF > README README for fcntl.patch This patch provides an almost complete implementation of the file locking facilities provided by fcntl(). The only missing pieces are - deadlock detection - mandatory locks Neither of these have a very high priority with me so I do not intend to implement them soon. This patch must be applied to Linux 0.97 Patch Level 1. Apply it with: cd /usr/src patch -p0 &1 | tee fcntlpatch.log Enjoy! Doug Evans, dje@sspiff.UUCP or dje@sspiff.ampr.ab.ca, 92Aug18 SHAR_EOF cat << \SHAR_EOF > fcntl.patch *** linux.pl1/fs/Makefile Sat Aug 1 15:44:01 1992 --- linux/fs/Makefile Sat Aug 8 00:02:15 1992 *************** *** 7,13 **** # # Note 2! The CFLAGS definitions are now in the main makefile... ! SUBDIRS =minix ext msdos .c.s: $(CC) $(CFLAGS) -S $< --- 7,13 ---- # # Note 2! The CFLAGS definitions are now in the main makefile... ! SUBDIRS =minix ext msdos # xenix .c.s: $(CC) $(CFLAGS) -S $< *************** *** 18,24 **** OBJS= open.o read_write.o inode.o file_table.o buffer.o super.o \ block_dev.o stat.o exec.o pipe.o namei.o fcntl.o ioctl.o \ ! select.o fifo.o all: fs.o fssubdirs --- 18,24 ---- OBJS= open.o read_write.o inode.o file_table.o buffer.o super.o \ block_dev.o stat.o exec.o pipe.o namei.o fcntl.o ioctl.o \ ! select.o fifo.o locks.o all: fs.o fssubdirs *** linux.pl1/fs/fcntl.c Tue Aug 18 12:51:57 1992 --- linux/fs/fcntl.c Tue Aug 18 13:11:57 1992 *************** *** 14,19 **** --- 14,21 ---- #include extern int sys_close(int fd); + extern int fcntl_getlk(unsigned int, struct flock *); + extern int fcntl_setlk(unsigned int, unsigned int, struct flock *); static int dupfd(unsigned int fd, unsigned int arg) { *************** *** 56,85 **** if (fd >= NR_OPEN || !(filp = current->filp[fd])) return -EBADF; switch (cmd) { ! case F_DUPFD: ! return dupfd(fd,arg); ! case F_GETFD: ! return (current->close_on_exec>>fd)&1; ! case F_SETFD: ! if (arg&1) ! current->close_on_exec |= (1<close_on_exec &= ~(1<f_flags; ! case F_SETFL: ! filp->f_flags &= ~(O_APPEND | O_NONBLOCK); ! filp->f_flags |= arg & (O_APPEND | O_NONBLOCK); ! return 0; ! case F_GETLK: case F_SETLK: case F_SETLKW: ! return -ENOSYS; ! default: ! /* sockets need a few special fcntls. */ ! if (S_ISSOCK (filp->f_inode->i_mode)) ! { ! return (sock_fcntl (filp, cmd, arg)); ! } ! return -EINVAL; } } --- 58,89 ---- if (fd >= NR_OPEN || !(filp = current->filp[fd])) return -EBADF; switch (cmd) { ! case F_DUPFD: ! return dupfd(fd,arg); ! case F_GETFD: ! return (current->close_on_exec>>fd)&1; ! case F_SETFD: ! if (arg&1) ! current->close_on_exec |= (1<close_on_exec &= ~(1<f_flags; ! case F_SETFL: ! filp->f_flags &= ~(O_APPEND | O_NONBLOCK); ! filp->f_flags |= arg & (O_APPEND | O_NONBLOCK); ! return 0; ! case F_GETLK: ! return fcntl_getlk(fd, (struct flock *) arg); ! case F_SETLK: ! return fcntl_setlk(fd, cmd, (struct flock *) arg); ! case F_SETLKW: ! return fcntl_setlk(fd, cmd, (struct flock *) arg); ! default: ! /* sockets need a few special fcntls. */ ! if (S_ISSOCK (filp->f_inode->i_mode)) ! return sock_fcntl (filp, cmd, arg); ! return -EINVAL; } } *** linux.pl1/fs/open.c Tue Aug 18 12:51:57 1992 --- linux/fs/open.c Mon Aug 17 13:31:22 1992 *************** *** 17,22 **** --- 17,24 ---- #include #include + extern void fcntl_remove_locks(struct task_struct *, struct file *); + struct file_operations * chrdev_fops[MAX_CHRDEV] = { NULL, }; *************** *** 339,347 **** } static int ! close_fp (struct file *filp) ! { ! struct inode *inode; if (filp->f_count == 0) { printk("Close: file count is 0\n"); --- 341,349 ---- } static int ! close_fp(struct file *filp) ! { ! struct inode * inode; if (filp->f_count == 0) { printk("Close: file count is 0\n"); *************** *** 348,359 **** return 0; } if (filp->f_count > 1) { filp->f_count--; return 0; } ! ! inode = filp->f_inode; if (filp->f_op && filp->f_op->release) filp->f_op->release(inode,filp); --- 350,364 ---- return 0; } + inode = filp->f_inode; + if (S_ISREG(inode->i_mode)) + fcntl_remove_locks(current, filp); + if (filp->f_count > 1) { filp->f_count--; return 0; } ! if (filp->f_op && filp->f_op->release) filp->f_op->release(inode,filp); *** linux.pl1/fs/locks.c Tue Aug 11 14:27:34 1992 --- linux/fs/locks.c Tue Aug 18 13:18:51 1992 *************** *** 0 **** --- 1,451 ---- + /* + * linux/fs/locks.c + * + * Provide support for fcntl()'s F_GETLK, F_SETLK, and F_SETLKW calls. + * Doug Evans, 92Aug07, dje@sspiff.UUCP. + * + * FIXME: two things aren't handled yet: + * - deadlock detection/avoidance (of dubious merit, but since it's in + * the definition, I guess it should be provided eventually) + * - mandatory locks (requires lots of changes elsewhere) + * + * FIXME: Should vector locking operations through table based on filesystem + * type. NFS mounts obviously won't be able to use this code. + */ + + #include + + #include + #include + #include + #include + #include + + #define OFFSET_MAX 0x7fffffff /* FIXME: move elsewhere? */ + + static int copy_flock(struct file *filp, struct file_lock *fl, struct flock *l); + static int conflict(struct file_lock *caller_fl, struct file_lock *sys_fl); + static int overlap(struct file_lock *fl1, struct file_lock *fl2); + static int lock_it(struct file *filp, struct file_lock *caller); + static int unlock_it(struct file *filp, struct file_lock *caller); + static struct file_lock *alloc_lock(struct file *filp, struct file_lock *template); + static void free_lock(struct file *filp, struct file_lock *fl); + + static struct file_lock file_lock_table[NR_FILE_LOCKS]; + static struct file_lock *file_lock_free_list; + + /* + * Called at boot time to initialize the lock table ... + */ + + void fcntl_init_locks(void) + { + struct file_lock *fl; + + for (fl = &file_lock_table[0]; fl < file_lock_table + NR_FILE_LOCKS - 1; fl++) { + fl->fl_next = fl + 1; + fl->fl_owner = NULL; + } + file_lock_table[NR_FILE_LOCKS - 1].fl_next = NULL; + file_lock_table[NR_FILE_LOCKS - 1].fl_owner = NULL; + file_lock_free_list = &file_lock_table[0]; + } + + int fcntl_getlk(unsigned int fd, struct flock *l) + { + struct flock flock; + struct file *filp; + struct file_lock *fl,file_lock; + + if (fd >= NR_OPEN || !(filp = current->filp[fd])) + return -EBADF; + verify_area(l, sizeof(*l)); + memcpy_fromfs(&flock, l, sizeof(flock)); + if (flock.l_type == F_UNLCK) + return -EINVAL; + if (!copy_flock(filp, &file_lock, &flock)) + return -EINVAL; + + for (fl = filp->f_inode->i_flock; fl != NULL; fl = fl->fl_next) { + if (conflict(&file_lock, fl)) { + flock.l_pid = fl->fl_owner->pid; + flock.l_start = fl->fl_start; + flock.l_len = fl->fl_end == OFFSET_MAX ? 0 : + fl->fl_end - fl->fl_start + 1; + /* Note: this will always be SEEK_SET. */ + flock.l_whence = fl->fl_whence; + flock.l_type = fl->fl_type; + memcpy_tofs(l, &flock, sizeof(flock)); + return 0; + } + } + + flock.l_type = F_UNLCK; /* no conflict found */ + memcpy_tofs(l, &flock, sizeof(flock)); + return 0; + } + + /* + * This function implements both F_SETLK and F_SETLKW. + */ + + int fcntl_setlk(unsigned int fd, unsigned int cmd, struct flock *l) + { + struct file *filp; + struct file_lock *fl,file_lock; + struct flock flock; + + /* + * Get arguments and validate them ... + */ + + if (fd >= NR_OPEN || !(filp = current->filp[fd])) + return -EBADF; + verify_area(l, sizeof(*l)); + memcpy_fromfs(&flock, l, sizeof(flock)); + if (!copy_flock(filp, &file_lock, &flock)) + return -EINVAL; + switch (file_lock.fl_type) { + case F_RDLCK : + if (!(filp->f_mode & 1)) + return -EBADF; + break; + case F_WRLCK : + if (!(filp->f_mode & 2)) + return -EBADF; + break; + case F_UNLCK : + break; + } + + /* + * F_UNLCK needs to be handled differently ... + */ + + if (file_lock.fl_type == F_UNLCK) + return unlock_it(filp, &file_lock); + + /* + * Scan for a conflicting lock ... + */ + + repeat: + for (fl = filp->f_inode->i_flock; fl != NULL; fl = fl->fl_next) { + if (!conflict(&file_lock, fl)) + continue; + /* + * File is locked by another process. If this is F_SETLKW + * wait for the lock to be released. + * FIXME: We need to check for deadlocks here. + */ + if (cmd == F_SETLKW) { + interruptible_sleep_on(&fl->fl_wait); + goto repeat; + } + return -EAGAIN; + } + + /* + * Lock doesn't conflict with any other lock ... + */ + + return lock_it(filp, &file_lock); + } + + /* + * This function is called when the file is closed. + */ + + void fcntl_remove_locks(struct task_struct *task, struct file *filp) + { + struct file_lock *fl,*next; + + for (fl = filp->f_inode->i_flock; fl != NULL; ) { + /* + * If this one is freed, gets clobbered when the + * entry is moved to the free list, so grab it now ... + */ + next = fl->fl_next; + if (fl->fl_owner == task) + free_lock(filp, fl); + fl = next; + } + } + + /* + * Verify a "struct flock" and copy it to a "struct file_lock" ... + * Result is a boolean indicating success. + */ + + static int copy_flock(struct file *filp, struct file_lock *fl, struct flock *l) + { + off_t start; + + if (!filp->f_inode) /* just in case */ + return 0; + if (!S_ISREG(filp->f_inode->i_mode)) + return 0; + if (l->l_type != F_UNLCK && l->l_type != F_RDLCK && l->l_type != F_WRLCK) + return 0; + switch (l->l_whence) { + case 0 /*SEEK_SET*/ : start = 0; break; + case 1 /*SEEK_CUR*/ : start = filp->f_pos; break; + case 2 /*SEEK_END*/ : start = filp->f_inode->i_size; break; + default : return 0; + } + if ((start += l->l_start) < 0 || l->l_len < 0) + return 0; + fl->fl_type = l->l_type; + fl->fl_start = start; /* we record the absolute position */ + fl->fl_whence = 0; /* FIXME: do we record as passed? */ + if (l->l_len == 0 || (fl->fl_end = start + l->l_len - 1) < 0) + fl->fl_end = OFFSET_MAX; + fl->fl_owner = current; + fl->fl_wait = NULL; /* just for cleanliness */ + return 1; + } + + /* + * Determine if lock blocks lock ... + */ + + static int conflict(struct file_lock *caller_fl, struct file_lock *sys_fl) + { + if (caller_fl->fl_owner == sys_fl->fl_owner) + return 0; + if (!overlap(caller_fl, sys_fl)) + return 0; + switch (caller_fl->fl_type) { + case F_RDLCK : + return sys_fl->fl_type != F_RDLCK; + case F_WRLCK : + return 1; /* overlapping region not owned by caller */ + } + return 0; /* shouldn't get here, but just in case */ + } + + static int overlap(struct file_lock *fl1, struct file_lock *fl2) + { + if (fl1->fl_start <= fl2->fl_start) { + return fl1->fl_end >= fl2->fl_start; + } else { + return fl2->fl_end >= fl1->fl_start; + } + } + + /* + * Add a lock to a file ... + * Result is 0 for success or -ENOLCK. + * + * We try to be real clever here and always minimize the number of table + * entries we use. For example we merge adjacent locks whenever possible. This + * consumes a bit of cpu and code space, is it really worth it? Beats me. + * + * I've tried to keep the following as small and simple as possible. If you can + * make it smaller or simpler, please do. /dje 92Aug11 + * + * WARNING: We assume the lock doesn't conflict with any other lock. + */ + + static int lock_it(struct file *filp, struct file_lock *caller) + { + struct file_lock *fl,*new; + + /* + * It's easier if we allocate a slot for the lock first, and then + * release it later if we have to (IE: if it can be merged with + * another). This way the for() loop always knows that is an + * existing entry. This will cause the routine to fail unnecessarily + * in rare cases, but perfection can be pushed too far. :-) + */ + + if ((caller = alloc_lock(filp, caller)) == NULL) + return -ENOLCK; + + /* + * First scan to see if we are changing/augmenting an existing lock ... + */ + + for (fl = filp->f_inode->i_flock; fl != NULL; fl = fl->fl_next) { + if (caller->fl_owner != fl->fl_owner) + continue; + if (caller == fl) + continue; + if (!overlap(caller, fl)) { + /* + * Detect adjacent regions (if same lock type) ... + */ + if (caller->fl_type != fl->fl_type) + continue; + if (caller->fl_end + 1 == fl->fl_start) { + fl->fl_start = caller->fl_start; + free_lock(filp, caller); + caller = fl; + /* must continue, may overlap others now */ + } else if (caller->fl_start - 1 == fl->fl_end) { + fl->fl_end = caller->fl_end; + free_lock(filp, caller); + caller = fl; + /* must continue, may overlap others now */ + } + continue; + } + /* + * We've found an overlapping region. Is it a change of lock + * type, or are we changing the size of the locked space? + */ + if (caller->fl_type != fl->fl_type) { + if (caller->fl_start > fl->fl_start && caller->fl_end < fl->fl_end) { + /* + * The new lock splits the old one in two ... + * is the bottom piece, is the + * new lock, and is the top piece. + */ + if ((new = alloc_lock(filp, fl)) == NULL) { + free_lock(filp, caller); + return -ENOLCK; + } + fl->fl_end = caller->fl_start - 1; + new->fl_start = caller->fl_end + 1; + return 0; + } + if (caller->fl_start <= fl->fl_start && caller->fl_end >= fl->fl_end) { + /* + * The new lock completely replaces old one ... + */ + free_lock(filp, fl); + return 0; + } + if (caller->fl_end < fl->fl_end) { + fl->fl_start = caller->fl_end + 1; + /* must continue, may be more overlaps */ + } else if (caller->fl_start > fl->fl_start) { + fl->fl_end = caller->fl_start - 1; + /* must continue, may be more overlaps */ + } else { + printk("lock_it: program bug: unanticipated overlap\n"); + free_lock(filp, caller); + return -ENOLCK; + } + } else { /* The new lock augments an existing lock ... */ + int grew = 0; + + if (caller->fl_start < fl->fl_start) { + fl->fl_start = caller->fl_start; + grew = 1; + } + if (caller->fl_end > fl->fl_end) { + fl->fl_end = caller->fl_end; + grew = 1; + } + free_lock(filp, caller); + caller = fl; + if (!grew) + return 0; + /* must continue, may be more overlaps */ + } + } + + /* + * New lock doesn't overlap any regions ... + * alloc_lock() has already been called, so we're done! + */ + + return 0; + } + + /* + * Handle F_UNLCK ... + * Result is 0 for success, or -EINVAL or -ENOLCK. + * ENOLCK can happen when a lock is split into two. + */ + + static int unlock_it(struct file *filp, struct file_lock *caller) + { + int one_unlocked = 0; + struct file_lock *fl,*next; + + for (fl = filp->f_inode->i_flock; fl != NULL; ) { + if (caller->fl_owner != fl->fl_owner || !overlap(caller, fl)) { + fl = fl->fl_next; + continue; + } + one_unlocked = 1; + if (caller->fl_start > fl->fl_start && caller->fl_end < fl->fl_end) { + /* + * Lock is split in two ... + * is the bottom piece, is the top piece. + */ + if ((next = alloc_lock(filp, fl)) == NULL) + return -ENOLCK; + fl->fl_end = caller->fl_start - 1; + next->fl_start = caller->fl_end + 1; + return 0; + } + if (caller->fl_start <= fl->fl_start && caller->fl_end >= fl->fl_end) { + /* + * gets clobbered when the entry is moved to + * the free list, so grab it now ... + */ + next = fl->fl_next; + free_lock(filp, fl); + fl = next; + } + if (caller->fl_start == fl->fl_start && caller->fl_end == fl->fl_end) + return 0; /* no more to be found */ + /* must continue, there may be more to unlock */ + } + + return one_unlocked ? 0 : -EINVAL; + } + + static struct file_lock *alloc_lock(struct file *filp, struct file_lock *template) + { + struct file_lock *new; + + if (file_lock_free_list == NULL) + return NULL; /* no available entry */ + if (file_lock_free_list->fl_owner != NULL) + panic("alloc_lock: broken free list\n"); + + new = file_lock_free_list; /* remove from free list */ + file_lock_free_list = file_lock_free_list->fl_next; + + *new = *template; + + new->fl_next = filp->f_inode->i_flock; /* insert into file's list */ + filp->f_inode->i_flock = new; + + new->fl_owner = current; /* FIXME: needed? */ + new->fl_wait = NULL; + return new; + } + + static void free_lock(struct file *filp, struct file_lock *fl) + { + struct file_lock **fl_p; + + if (fl->fl_owner == NULL) /* sanity check */ + panic("free_lock: broken lock list\n"); + + /* + * We only use a singly linked list to save some memory space + * (the only place we'd use a doubly linked list is here). + */ + + for (fl_p = &filp->f_inode->i_flock; *fl_p != NULL; fl_p = &(*fl_p)->fl_next) { + if (*fl_p == fl) + break; + } + if (*fl_p == NULL) { + printk("free_lock: lock is not in file's lock list\n"); + } else { + *fl_p = (*fl_p)->fl_next; + } + + fl->fl_next = file_lock_free_list; /* add to free list */ + file_lock_free_list = fl; + fl->fl_owner = NULL; /* for sanity checks */ + + wake_up(&fl->fl_wait); + } *** linux.pl1/fs/super.c Sat Aug 1 13:09:35 1992 --- linux/fs/super.c Mon Aug 17 13:54:39 1992 *************** *** 12,17 **** --- 12,18 ---- #include #include #include + /*#include */ #include #include #include *************** *** 21,26 **** --- 22,28 ---- int sync_dev(int dev); void wait_for_keypress(void); + void fcntl_init_locks(void); /* set_bit uses setb, as gas doesn't recognize setc */ #define set_bit(bitnr,addr) ({ \ *************** *** 35,42 **** /* Move into include file later */ static struct file_system_type file_systems[] = { {minix_read_super,"minix"}, ! {ext_read_super,"ext"}, {msdos_read_super,"msdos"}, {NULL,NULL} }; --- 37,45 ---- /* Move into include file later */ static struct file_system_type file_systems[] = { + /*{xenix_read_super,"xenix"},*/ {minix_read_super,"minix"}, ! /*{ext_read_super,"ext"},*/ {msdos_read_super,"msdos"}, {NULL,NULL} }; *************** *** 314,319 **** --- 317,323 ---- panic("bad i-node size"); for(i=0;i