int locks_mandatory_area(int read_write, struct inode *inode, struct file *filp, unsigned int offset, unsigned int count) { struct file_lock *fl; struct file_lock tfl; memset(&tfl, 0, sizeof(tfl)); tfl.fl_file = filp; tfl.fl_flags = FL_POSIX | FL_ACCESS; tfl.fl_owner = current; tfl.fl_type = (read_write == FLOCK_VERIFY_WRITE) ? F_WRLCK : F_RDLCK; tfl.fl_start = offset; tfl.fl_end = offset + count - 1; repeat: /* If there are no FL_POSIX locks then go ahead. */ if (!(fl = inode->i_flock) || !(fl->fl_flags & FL_POSIX)) return (0); /* Search the lock list for this inode for locks that conflict with * the proposed read/write. */ while (fl != NULL) { /* Block for writes against a "read" lock, * and both reads and writes against a "write" lock. */ if (posix_locks_conflict(fl, &tfl)) { if (filp && (filp->f_flags & O_NONBLOCK)) return (-EAGAIN); if (current->signal & ~current->blocked) return (-ERESTARTSYS); if (posix_locks_deadlock(current, fl->fl_owner)) return (-EDEADLK); locks_insert_block(fl, &tfl); interruptible_sleep_on(&tfl.fl_wait); locks_delete_block(fl, &tfl); if (current->signal & ~current->blocked) return (-ERESTARTSYS); /* If we've been sleeping someone might have * changed the permissions behind our back. */ if ((inode->i_mode & (S_ISGID | S_IXGRP)) != S_ISGID) break; goto repeat; } fl = fl->fl_next; } return (0); }
int locks_mandatory_area(int read_write, struct inode *inode, struct file *filp, unsigned int offset, unsigned int count) { #ifdef CONFIG_LOCK_MANDATORY struct file_lock *fl; repeat: /* Check that there are locks, and that they're not F_FLOCK locks. */ if ((fl = inode->i_flock) && (fl->fl_flags & F_FLOCK)) return (0); /* * Search the lock list for this inode for locks that conflict with * the proposed read/write. */ while (fl != NULL) { if (fl->fl_owner == current || fl->fl_end < offset || fl->fl_start >= offset + count) goto next_lock; /* * Block for writes against a "read" lock, * and both reads and writes against a "write" lock. */ if ((read_write == FLOCK_VERIFY_WRITE) || (fl->fl_type == F_WRLCK)) { if (filp && (filp->f_flags & O_NONBLOCK)) return (-EAGAIN); if (current->signal & ~current->blocked) return (-ERESTARTSYS); if (posix_locks_deadlock(current, fl->fl_owner)) return (-EDEADLK); interruptible_sleep_on(&fl->fl_wait); if (current->signal & ~current->blocked) return (-ERESTARTSYS); /* * If we've been sleeping someone might have * changed the permissions behind our back. */ if ((inode->i_mode & (S_ISGID | S_IXGRP)) != S_ISGID) break; goto repeat; } next_lock: fl = fl->fl_next; } #endif return (0); }
static int posix_lock_file(struct file *filp, struct file_lock *caller, unsigned int wait) { struct file_lock *fl; struct file_lock *new_fl, *new_fl2; struct file_lock *left = NULL; struct file_lock *right = NULL; struct file_lock **before; int error; int added = 0; /* * We may need two file_lock structures for this operation, * so we get them in advance to avoid races. */ new_fl = locks_empty_lock(); new_fl2 = locks_empty_lock(); error = -ENOLCK; /* "no luck" */ if (!(new_fl && new_fl2)) goto out; if (caller->fl_type != F_UNLCK) { repeat: error = -EBUSY; if ((fl = filp->f_inode->i_flock) && (fl->fl_flags & FL_FLOCK)) goto out; while (fl != NULL) { if (!posix_locks_conflict(caller, fl)) { fl = fl->fl_next; continue; } error = -EAGAIN; if (!wait) goto out; error = -EDEADLK; if (posix_locks_deadlock(caller->fl_owner, fl->fl_owner)) goto out; error = -ERESTARTSYS; if (current->signal & ~current->blocked) goto out; locks_insert_block(fl, caller); interruptible_sleep_on(&caller->fl_wait); locks_delete_block(fl, caller); goto repeat; } } /* * We've allocated the new locks in advance, so there are no * errors possible (and no blocking operations) from here on. * * Find the first old lock with the same owner as the new lock. */ before = &filp->f_inode->i_flock; error = -EBUSY; if ((*before != NULL) && ((*before)->fl_flags & FL_FLOCK)) goto out; /* First skip locks owned by other processes. */ while ((fl = *before) && (caller->fl_owner != fl->fl_owner)) { before = &fl->fl_next; } /* Process locks with this owner. */ while ((fl = *before) && (caller->fl_owner == fl->fl_owner)) { /* Detect adjacent or overlapping regions (if same lock type) */ if (caller->fl_type == fl->fl_type) { if (fl->fl_end < caller->fl_start - 1) goto next_lock; /* If the next lock in the list has entirely bigger * addresses than the new one, insert the lock here. */ if (fl->fl_start > caller->fl_end + 1) break; /* If we come here, the new and old lock are of the * same type and adjacent or overlapping. Make one * lock yielding from the lower start address of both * locks to the higher end address. */ if (fl->fl_start > caller->fl_start) fl->fl_start = caller->fl_start; else caller->fl_start = fl->fl_start; if (fl->fl_end < caller->fl_end) fl->fl_end = caller->fl_end; else caller->fl_end = fl->fl_end; if (added) { locks_delete_lock(before, 0); continue; } caller = fl; added = 1; } else { /* Processing for different lock types is a bit * more complex. */ if (fl->fl_end < caller->fl_start) goto next_lock; if (fl->fl_start > caller->fl_end) break; if (caller->fl_type == F_UNLCK) added = 1; if (fl->fl_start < caller->fl_start) left = fl; /* If the next lock in the list has a higher end * address than the new one, insert the new one here. */ if (fl->fl_end > caller->fl_end) { right = fl; break; } if (fl->fl_start >= caller->fl_start) { /* The new lock completely replaces an old * one (This may happen several times). */ if (added) { locks_delete_lock(before, 0); continue; } /* Replace the old lock with the new one. * Wake up anybody waiting for the old one, * as the change in lock type might satisfy * their needs. */ locks_wake_up_blocks(fl, 0); fl->fl_start = caller->fl_start; fl->fl_end = caller->fl_end; fl->fl_type = caller->fl_type; caller = fl; added = 1; } } /* Go on to next lock. */ next_lock: before = &fl->fl_next; } error = 0; if (!added) { if (caller->fl_type == F_UNLCK) goto out; locks_init_lock(new_fl, caller); locks_insert_lock(before, new_fl); new_fl = NULL; } if (right) { if (left == right) { /* The new lock breaks the old one in two pieces, * so we have to use the second new lock (in this * case, even F_UNLCK may fail!). */ left = locks_init_lock(new_fl2, right); locks_insert_lock(before, left); new_fl2 = NULL; } right->fl_start = caller->fl_end + 1; locks_wake_up_blocks(right, 0); } if (left) { left->fl_end = caller->fl_start - 1; locks_wake_up_blocks(left, 0); } out: /* * Free any unused locks. (They haven't * ever been used, so we use kfree().) */ if (new_fl) kfree(new_fl); if (new_fl2) kfree(new_fl2); return error; }
/* * Attempt to establish a lock, and if it can't be granted, block it * if required. */ u32 nlmsvc_lock(struct svc_rqst *rqstp, struct nlm_file *file, struct nlm_lock *lock, int wait, struct nlm_cookie *cookie) { struct file_lock *conflock; struct nlm_block *block, *nblock = NULL; int error; dprintk("lockd: nlmsvc_lock(%04x/%ld, ty=%d, pi=%d, %Ld-%Ld, bl=%d)\n", file->f_file.f_dentry->d_inode->i_dev, file->f_file.f_dentry->d_inode->i_ino, lock->fl.fl_type, lock->fl.fl_pid, (long long)lock->fl.fl_start, (long long)lock->fl.fl_end, wait); /* Get existing block (in case client is busy-waiting) */ block = nlmsvc_lookup_block(file, lock, 0); lock->fl.fl_flags |= FL_LOCKD; again: /* Lock file against concurrent access */ down(&file->f_sema); if (!(conflock = posix_test_lock(&file->f_file, &lock->fl))) { error = posix_lock_file(&file->f_file, &lock->fl, 0); if (block) nlmsvc_delete_block(block, 0); up(&file->f_sema); dprintk("lockd: posix_lock_file returned %d\n", -error); switch(-error) { case 0: return nlm_granted; case EDEADLK: return nlm_deadlock; case EAGAIN: return nlm_lck_denied; default: /* includes ENOLCK */ return nlm_lck_denied_nolocks; } } if (!wait) { up(&file->f_sema); return nlm_lck_denied; } if (posix_locks_deadlock(&lock->fl, conflock)) { if (nblock) nlmsvc_delete_block(nblock, 0); up(&file->f_sema); return nlm_deadlock; } /* If we don't have a block, create and initialize it. Then * retry because we may have slept in kmalloc. */ /* We have to release f_sema as nlmsvc_create_block may try to * to claim it while doing host garbage collection */ if (block == NULL) { up(&file->f_sema); dprintk("lockd: blocking on this lock (allocating).\n"); if (!(block = nlmsvc_create_block(rqstp, file, lock, cookie))) return nlm_lck_denied_nolocks; nblock = block; goto again; } /* Append to list of blocked */ nlmsvc_insert_block(block, NLM_NEVER); if (list_empty(&block->b_call.a_args.lock.fl.fl_block)) { /* Now add block to block list of the conflicting lock if we haven't done so. */ dprintk("lockd: blocking on this lock.\n"); posix_block_lock(conflock, &block->b_call.a_args.lock.fl); } up(&file->f_sema); return nlm_lck_blocked; }
static int posix_lock_file(struct file *filp, struct file_lock *caller, unsigned int wait) { struct file_lock *fl; struct file_lock *new_fl; struct file_lock *left = NULL; struct file_lock *right = NULL; struct file_lock **before; int added = 0; repeat: if ((fl = filp->f_inode->i_flock) && (fl->fl_flags & F_FLOCK)) return (-EBUSY); if (caller->fl_type != F_UNLCK) { while (fl != NULL) { if (posix_locks_conflict(caller, fl)) { if (!wait) return (-EAGAIN); if (current->signal & ~current->blocked) return (-ERESTARTSYS); if (posix_locks_deadlock(caller->fl_owner, fl->fl_owner)) return (-EDEADLK); interruptible_sleep_on(&fl->fl_wait); if (current->signal & ~current->blocked) return (-ERESTARTSYS); goto repeat; } fl = fl->fl_next; } } /* * Find the first old lock with the same owner as the new lock. */ before = &filp->f_inode->i_flock; /* First skip FLOCK locks and locks owned by other processes. */ while ((fl = *before) && (caller->fl_owner != fl->fl_owner)) { before = &fl->fl_next; } /* Process locks with this owner. */ while ((fl = *before) && (caller->fl_owner == fl->fl_owner)) { /* Detect adjacent or overlapping regions (if same lock type) */ if (caller->fl_type == fl->fl_type) { if (fl->fl_end < caller->fl_start - 1) goto next_lock; /* If the next lock in the list has entirely bigger * addresses than the new one, insert the lock here. */ if (fl->fl_start > caller->fl_end + 1) break; /* If we come here, the new and old lock are of the * same type and adjacent or overlapping. Make one * lock yielding from the lower start address of both * locks to the higher end address. */ if (fl->fl_start > caller->fl_start) fl->fl_start = caller->fl_start; else caller->fl_start = fl->fl_start; if (fl->fl_end < caller->fl_end) fl->fl_end = caller->fl_end; else caller->fl_end = fl->fl_end; if (added) { locks_delete_lock(before, 0); continue; } caller = fl; added = 1; } else { /* Processing for different lock types is a bit * more complex. */ if (fl->fl_end < caller->fl_start) goto next_lock; if (fl->fl_start > caller->fl_end) break; if (caller->fl_type == F_UNLCK) added = 1; if (fl->fl_start < caller->fl_start) left = fl; /* If the next lock in the list has a higher end * address than the new one, insert the new one here. */ if (fl->fl_end > caller->fl_end) { right = fl; break; } if (fl->fl_start >= caller->fl_start) { /* The new lock completely replaces an old * one (This may happen several times). */ if (added) { locks_delete_lock(before, 0); continue; } /* Replace the old lock with the new one. * Wake up anybody waiting for the old one, * as the change in lock type might satisfy * their needs. */ wake_up(&fl->fl_wait); fl->fl_start = caller->fl_start; fl->fl_end = caller->fl_end; fl->fl_type = caller->fl_type; caller = fl; added = 1; } } /* Go on to next lock. */ next_lock: before = &fl->fl_next; } if (!added) { if (caller->fl_type == F_UNLCK) return (0); if ((new_fl = locks_alloc_lock(caller)) == NULL) return (-ENOLCK); locks_insert_lock(before, new_fl); } if (right) { if (left == right) { /* The new lock breaks the old one in two pieces, so we * have to allocate one more lock (in this case, even * F_UNLCK may fail!). */ if ((left = locks_alloc_lock(right)) == NULL) { if (!added) locks_delete_lock(before, 0); return (-ENOLCK); } locks_insert_lock(before, left); } right->fl_start = caller->fl_end + 1; wake_up(&right->fl_wait); } if (left) { left->fl_end = caller->fl_start - 1; wake_up(&left->fl_wait); } return (0); }