xref: /minix/minix/servers/vfs/lock.c (revision 117b6ea0)
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
173 lock_revive(void)
174 {
175 /* Go find all the processes that are waiting for any kind of lock and
176  * revive them all.  The ones that are still blocked will block again when
177  * they run.  The others will complete.  This strategy is a space-time
178  * tradeoff.  Figuring out exactly which ones to unblock now would take
179  * extra code, and the only thing it would win would be some performance in
180  * extremely rare circumstances (namely, that somebody actually used
181  * locking).
182  */
183 
184   struct fproc *fptr;
185 
186   for (fptr = &fproc[0]; fptr < &fproc[NR_PROCS]; fptr++){
187 	if (fptr->fp_pid == PID_FREE) continue;
188 	if (fptr->fp_blocked_on == FP_BLOCKED_ON_FLOCK) {
189 		revive(fptr->fp_endpoint, 0);
190 	}
191   }
192 }
193