1 /* This file handles advisory file locking as required by POSIX. 2 * 3 * The entry points into this file are 4 * lock_op: perform locking operations for FCNTL system call 5 * lock_revive: revive processes when a lock is released 6 */ 7 8 #include "fs.h" 9 #include <minix/com.h> 10 #include <minix/u64.h> 11 #include <fcntl.h> 12 #include <unistd.h> 13 #include <assert.h> 14 #include "file.h" 15 #include "lock.h" 16 #include "vnode.h" 17 18 /*===========================================================================* 19 * lock_op * 20 *===========================================================================*/ 21 int lock_op(int fd, int req, vir_bytes arg) 22 { 23 /* Perform the advisory locking required by POSIX. */ 24 int r, ltype, i, conflict = 0, unlocking = 0; 25 mode_t mo; 26 off_t first, last; 27 struct filp *f; 28 struct flock flock; 29 struct file_lock *flp, *flp2, *empty; 30 31 assert(req == F_GETLK || req == F_SETLK || req == F_SETLKW); 32 33 f = fp->fp_filp[fd]; 34 assert(f != NULL); 35 36 /* Fetch the flock structure from user space. */ 37 r = sys_datacopy_wrapper(who_e, arg, VFS_PROC_NR, (vir_bytes)&flock, 38 sizeof(flock)); 39 if (r != OK) return(EINVAL); 40 41 /* Make some error checks. */ 42 ltype = flock.l_type; 43 mo = f->filp_mode; 44 if (ltype != F_UNLCK && ltype != F_RDLCK && ltype != F_WRLCK) return(EINVAL); 45 if (req == F_GETLK && ltype == F_UNLCK) return(EINVAL); 46 if (!S_ISREG(f->filp_vno->v_mode) && !S_ISBLK(f->filp_vno->v_mode)) 47 return(EINVAL); 48 if (req != F_GETLK && ltype == F_RDLCK && (mo & R_BIT) == 0) return(EBADF); 49 if (req != F_GETLK && ltype == F_WRLCK && (mo & W_BIT) == 0) return(EBADF); 50 51 /* Compute the first and last bytes in the lock region. */ 52 switch (flock.l_whence) { 53 case SEEK_SET: first = 0; break; 54 case SEEK_CUR: first = f->filp_pos; break; 55 case SEEK_END: first = f->filp_vno->v_size; break; 56 default: return(EINVAL); 57 } 58 59 /* Check for overflow. */ 60 if (((long) flock.l_start > 0) && ((first + flock.l_start) < first)) 61 return(EINVAL); 62 if (((long) flock.l_start < 0) && ((first + flock.l_start) > first)) 63 return(EINVAL); 64 first = first + flock.l_start; 65 last = first + flock.l_len - 1; 66 if (flock.l_len == 0) last = MAX_FILE_POS; 67 if (last < first) return(EINVAL); 68 69 /* Check if this region conflicts with any existing lock. */ 70 empty = NULL; 71 for (flp = &file_lock[0]; flp < &file_lock[NR_LOCKS]; flp++) { 72 if (flp->lock_type == 0) { 73 if (empty == NULL) empty = flp; 74 continue; /* 0 means unused slot */ 75 } 76 if (flp->lock_vnode != f->filp_vno) continue; /* different file */ 77 if (last < flp->lock_first) continue; /* new one is in front */ 78 if (first > flp->lock_last) continue; /* new one is afterwards */ 79 if (ltype == F_RDLCK && flp->lock_type == F_RDLCK) continue; 80 if (ltype != F_UNLCK && flp->lock_pid == fp->fp_pid) continue; 81 82 /* There might be a conflict. Process it. */ 83 conflict = 1; 84 if (req == F_GETLK) break; 85 86 /* If we are trying to set a lock, it just failed. */ 87 if (ltype == F_RDLCK || ltype == F_WRLCK) { 88 if (req == F_SETLK) { 89 /* For F_SETLK, just report back failure. */ 90 return(EAGAIN); 91 } else { 92 /* For F_SETLKW, suspend the process. */ 93 fp->fp_flock.fd = fd; 94 fp->fp_flock.cmd = req; 95 fp->fp_flock.arg = arg; 96 suspend(FP_BLOCKED_ON_FLOCK); 97 return(SUSPEND); 98 } 99 } 100 101 /* We are clearing a lock and we found something that overlaps. */ 102 unlocking = 1; 103 if (first <= flp->lock_first && last >= flp->lock_last) { 104 flp->lock_type = 0; /* mark slot as unused */ 105 nr_locks--; /* number of locks is now 1 less */ 106 continue; 107 } 108 109 /* Part of a locked region has been unlocked. */ 110 if (first <= flp->lock_first) { 111 flp->lock_first = last + 1; 112 continue; 113 } 114 115 if (last >= flp->lock_last) { 116 flp->lock_last = first - 1; 117 continue; 118 } 119 120 /* Bad luck. A lock has been split in two by unlocking the middle. */ 121 if (nr_locks == NR_LOCKS) return(ENOLCK); 122 for (i = 0; i < NR_LOCKS; i++) 123 if (file_lock[i].lock_type == 0) break; 124 flp2 = &file_lock[i]; 125 flp2->lock_type = flp->lock_type; 126 flp2->lock_pid = flp->lock_pid; 127 flp2->lock_vnode = flp->lock_vnode; 128 flp2->lock_first = last + 1; 129 flp2->lock_last = flp->lock_last; 130 flp->lock_last = first - 1; 131 nr_locks++; 132 } 133 if (unlocking) lock_revive(); 134 135 if (req == F_GETLK) { 136 if (conflict) { 137 /* GETLK and conflict. Report on the conflicting lock. */ 138 flock.l_type = flp->lock_type; 139 flock.l_whence = SEEK_SET; 140 flock.l_start = flp->lock_first; 141 flock.l_len = flp->lock_last - flp->lock_first + 1; 142 flock.l_pid = flp->lock_pid; 143 144 } else { 145 /* It is GETLK and there is no conflict. */ 146 flock.l_type = F_UNLCK; 147 } 148 149 /* Copy the flock structure back to the caller. */ 150 r = sys_datacopy_wrapper(VFS_PROC_NR, (vir_bytes)&flock, who_e, arg, 151 sizeof(flock)); 152 return(r); 153 } 154 155 if (ltype == F_UNLCK) return(OK); /* unlocked a region with no locks */ 156 157 /* There is no conflict. If space exists, store new lock in the table. */ 158 if (empty == NULL) return(ENOLCK); /* table full */ 159 empty->lock_type = ltype; 160 empty->lock_pid = fp->fp_pid; 161 empty->lock_vnode = f->filp_vno; 162 empty->lock_first = first; 163 empty->lock_last = last; 164 nr_locks++; 165 return(OK); 166 } 167 168 169 /*===========================================================================* 170 * lock_revive * 171 *===========================================================================*/ 172 void lock_revive() 173 { 174 /* Go find all the processes that are waiting for any kind of lock and 175 * revive them all. The ones that are still blocked will block again when 176 * they run. The others will complete. This strategy is a space-time 177 * tradeoff. Figuring out exactly which ones to unblock now would take 178 * extra code, and the only thing it would win would be some performance in 179 * extremely rare circumstances (namely, that somebody actually used 180 * locking). 181 */ 182 183 struct fproc *fptr; 184 185 for (fptr = &fproc[0]; fptr < &fproc[NR_PROCS]; fptr++){ 186 if (fptr->fp_pid == PID_FREE) continue; 187 if (fptr->fp_blocked_on == FP_BLOCKED_ON_FLOCK) { 188 revive(fptr->fp_endpoint, 0); 189 } 190 } 191 } 192