xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb_oplock.c (revision 8d0c3d29bb99f6521f2dc5058a7e4debebad7899)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
23  */
24 /*
25  * SMB Locking library functions.
26  *
27  * You will notice that the functions in this file exit the lock of the session
28  * and reenter it before returning. They even assume that the lock has been
29  * entered in READER mode. The reason for that is a potential deadlock that may
30  * occur when an oplock needs to be broken and the function
31  * smb_session_break_oplock() is called. It should be noticed that the mutex of
32  * the smb node, the oplock of which needs to be broken, is also exited before
33  * calling smb_session_break_oplock(). The reason for that is the same: avoiding
34  * a deadlock. That complexity is due to the fact that the lock of the session
35  * is held during the treatment of a request. That complexity will go away when
36  * that is not the case anymore.
37  */
38 
39 #include <smbsrv/smb_kproto.h>
40 #include <smbsrv/smb_fsops.h>
41 #include <inet/tcp.h>
42 
43 static void smb_oplock_wait(smb_node_t *);
44 
45 /*
46  *	Magic		0xFF 'S' 'M' 'B'
47  *	smb_com 	a byte, the "first" command
48  *	Error		a 4-byte union, ignored in a request
49  *	smb_flg		a one byte set of eight flags
50  *	smb_flg2	a two byte set of 16 flags
51  *	.		twelve reserved bytes, have a role
52  *			in connectionless transports (IPX, UDP?)
53  *	smb_tid		a 16-bit tree ID, a mount point sorta,
54  *			0xFFFF is this command does not have
55  *			or require a tree context
56  *	smb_pid		a 16-bit process ID
57  *	smb_uid		a 16-bit user ID, specific to this "session"
58  *			and mapped to a system (bona-fide) UID
59  *	smb_mid		a 16-bit multiplex ID, used to differentiate
60  *			multiple simultaneous requests from the same
61  *			process (pid) (ref RPC "xid")
62  *
63  * SMB_COM_LOCKING_ANDX allows both locking and/or unlocking of file range(s).
64  *
65  *  Client Request                     Description
66  *  ================================== =================================
67  *
68  *  UCHAR WordCount;                   Count of parameter words = 8
69  *  UCHAR AndXCommand;                 Secondary (X) command;  0xFF = none
70  *  UCHAR AndXReserved;                Reserved (must be 0)
71  *  USHORT AndXOffset;                 Offset to next command WordCount
72  *  USHORT Fid;                        File handle
73  *  UCHAR LockType;                    See LockType table below
74  *  UCHAR OplockLevel;                 The new oplock level
75  *  ULONG Timeout;                     Milliseconds to wait for unlock
76  *  USHORT NumberOfUnlocks;            Num. unlock range structs following
77  *  USHORT NumberOfLocks;              Num. lock range structs following
78  *  USHORT ByteCount;                  Count of data bytes
79  *  LOCKING_ANDX_RANGE Unlocks[];      Unlock ranges
80  *  LOCKING_ANDX_RANGE Locks[];        Lock ranges
81  *
82  *  LockType Flag Name            Value Description
83  *  ============================  ===== ================================
84  *
85  *  LOCKING_ANDX_SHARED_LOCK      0x01  Read-only lock
86  *  LOCKING_ANDX_OPLOCK_RELEASE   0x02  Oplock break notification
87  *  LOCKING_ANDX_CHANGE_LOCKTYPE  0x04  Change lock type
88  *  LOCKING_ANDX_CANCEL_LOCK      0x08  Cancel outstanding request
89  *  LOCKING_ANDX_LARGE_FILES      0x10  Large file locking format
90  *
91  *  LOCKING_ANDX_RANGE Format
92  *  =====================================================================
93  *
94  *  USHORT Pid;                        PID of process "owning" lock
95  *  ULONG Offset;                      Offset to bytes to [un]lock
96  *  ULONG Length;                      Number of bytes to [un]lock
97  *
98  *  Large File LOCKING_ANDX_RANGE Format
99  *  =====================================================================
100  *
101  *  USHORT Pid;                        PID of process "owning" lock
102  *  USHORT Pad;                        Pad to DWORD align (mbz)
103  *  ULONG OffsetHigh;                  Offset to bytes to [un]lock
104  *                                      (high)
105  *  ULONG OffsetLow;                   Offset to bytes to [un]lock (low)
106  *  ULONG LengthHigh;                  Number of bytes to [un]lock
107  *                                      (high)
108  *  ULONG LengthLow;                   Number of bytes to [un]lock (low)
109  *
110  *  Server Response                    Description
111  *  ================================== =================================
112  *
113  *  UCHAR WordCount;                   Count of parameter words = 2
114  *  UCHAR AndXCommand;                 Secondary (X) command;  0xFF =
115  *                                      none
116  *  UCHAR AndXReserved;                Reserved (must be 0)
117  *  USHORT AndXOffset;                 Offset to next command WordCount
118  *  USHORT ByteCount;                  Count of data bytes = 0
119  *
120  */
121 
122 /*
123  * smb_oplock_acquire
124  *
125  * Attempt to acquire an oplock. Note that the oplock granted may be
126  * none, i.e. the oplock was not granted. The result of the acquisition is
127  * provided in ol->ol_level.
128  *
129  * Grant an oplock to the requestor if this session is the only one
130  * that has the file open, regardless of the number of instances of
131  * the file opened by this session.
132  *
133  * However, if there is no oplock on this file and there is already
134  * at least one open, we will not grant an oplock, even if the only
135  * existing opens are from the same client.  This is "server discretion."
136  *
137  * An oplock may need to be broken in order for one to be granted, and
138  * depending on what action is taken by the other client (unlock or close),
139  * an oplock may or may not be granted.  (The breaking of an oplock is
140  * done earlier in the calling path.)
141  */
142 void
143 smb_oplock_acquire(smb_node_t *node, smb_ofile_t *of, smb_arg_open_t *op)
144 {
145 	smb_session_t	*session;
146 	smb_oplock_t	*ol;
147 	clock_t		time;
148 
149 	SMB_NODE_VALID(node);
150 	SMB_OFILE_VALID(of);
151 
152 	ASSERT(node == SMB_OFILE_GET_NODE(of));
153 
154 	session = SMB_OFILE_GET_SESSION(of);
155 
156 	if (!smb_session_oplocks_enable(session) ||
157 	    smb_tree_has_feature(SMB_OFILE_GET_TREE(of), SMB_TREE_NO_OPLOCKS)) {
158 		op->op_oplock_level = SMB_OPLOCK_NONE;
159 		return;
160 	}
161 
162 	ol = &node->n_oplock;
163 	time = MSEC_TO_TICK(smb_oplock_timeout) + ddi_get_lbolt();
164 
165 	mutex_enter(&node->n_mutex);
166 
167 	switch (node->n_state) {
168 	case SMB_NODE_STATE_OPLOCK_GRANTED:
169 		if (SMB_SESSION_GET_ID(session) == ol->ol_sess_id) {
170 			mutex_exit(&node->n_mutex);
171 			return;
172 		}
173 		break;
174 	case SMB_NODE_STATE_AVAILABLE:
175 	case SMB_NODE_STATE_OPLOCK_BREAKING:
176 		break;
177 	default:
178 		SMB_PANIC();
179 	}
180 
181 	for (;;) {
182 		int	rc;
183 
184 		smb_oplock_wait(node);
185 
186 		if (node->n_state == SMB_NODE_STATE_AVAILABLE) {
187 			if ((op->op_oplock_level == SMB_OPLOCK_LEVEL_II) ||
188 			    (op->op_oplock_level == SMB_OPLOCK_NONE) ||
189 			    (node->n_open_count > 1)) {
190 				mutex_exit(&node->n_mutex);
191 				op->op_oplock_level = SMB_OPLOCK_NONE;
192 				return;
193 			}
194 			ol->ol_ofile = of;
195 			ol->ol_sess_id = SMB_SESSION_GET_ID(session);
196 			ol->ol_level = op->op_oplock_level;
197 			ol->ol_xthread = curthread;
198 			node->n_state = SMB_NODE_STATE_OPLOCK_GRANTED;
199 			mutex_exit(&node->n_mutex);
200 			if (smb_fsop_oplock_install(node, of->f_mode) == 0) {
201 				smb_ofile_set_oplock_granted(of);
202 				return;
203 			}
204 			mutex_enter(&node->n_mutex);
205 			ASSERT(node->n_state == SMB_NODE_STATE_OPLOCK_GRANTED);
206 			node->n_state = SMB_NODE_STATE_AVAILABLE;
207 			ol->ol_xthread = NULL;
208 			op->op_oplock_level = SMB_OPLOCK_NONE;
209 			cv_broadcast(&ol->ol_cv);
210 			break;
211 		}
212 
213 		if (node->n_state == SMB_NODE_STATE_OPLOCK_GRANTED) {
214 			if (SMB_SESSION_GET_ID(session) == ol->ol_sess_id)
215 				break;
216 			node->n_state = SMB_NODE_STATE_OPLOCK_BREAKING;
217 			mutex_exit(&node->n_mutex);
218 			smb_session_oplock_break(
219 			    SMB_OFILE_GET_SESSION(ol->ol_ofile), ol->ol_ofile);
220 			mutex_enter(&node->n_mutex);
221 			continue;
222 		}
223 
224 		ASSERT(node->n_state == SMB_NODE_STATE_OPLOCK_BREAKING);
225 
226 		rc = cv_timedwait(&ol->ol_cv, &node->n_mutex, time);
227 
228 		if (rc == -1) {
229 			/*
230 			 * Oplock release timed out.
231 			 */
232 			if (node->n_state == SMB_NODE_STATE_OPLOCK_BREAKING) {
233 				node->n_state = SMB_NODE_STATE_AVAILABLE;
234 				ol->ol_xthread = curthread;
235 				mutex_exit(&node->n_mutex);
236 				smb_fsop_oplock_uninstall(node);
237 				mutex_enter(&node->n_mutex);
238 				ol->ol_xthread = NULL;
239 				cv_broadcast(&ol->ol_cv);
240 			}
241 		}
242 	}
243 	mutex_exit(&node->n_mutex);
244 }
245 
246 /*
247  * smb_oplock_break
248  *
249  * The oplock break may succeed for multiple reasons: file close, oplock
250  * release, holder connection dropped, requesting client disconnect etc.
251  *
252  * Returns:
253  *
254  *	B_TRUE	The oplock is broken.
255  *	B_FALSE	The oplock is being broken. This is returned if nowait is set
256  *		to B_TRUE;
257  */
258 boolean_t
259 smb_oplock_break(smb_node_t *node, smb_session_t *session, boolean_t nowait)
260 {
261 	smb_oplock_t	*ol;
262 	clock_t		time;
263 
264 	SMB_NODE_VALID(node);
265 	ol = &node->n_oplock;
266 	time = MSEC_TO_TICK(smb_oplock_timeout) + ddi_get_lbolt();
267 
268 	if (session != NULL) {
269 		mutex_enter(&node->n_mutex);
270 		if (SMB_SESSION_GET_ID(session) == ol->ol_sess_id) {
271 			mutex_exit(&node->n_mutex);
272 			return (B_TRUE);
273 		}
274 	} else {
275 		mutex_enter(&node->n_mutex);
276 	}
277 
278 	for (;;) {
279 		int	rc;
280 
281 		smb_oplock_wait(node);
282 
283 		if (node->n_state == SMB_NODE_STATE_AVAILABLE) {
284 			mutex_exit(&node->n_mutex);
285 			return (B_TRUE);
286 		}
287 
288 		if (node->n_state == SMB_NODE_STATE_OPLOCK_GRANTED) {
289 			node->n_state = SMB_NODE_STATE_OPLOCK_BREAKING;
290 			mutex_exit(&node->n_mutex);
291 			smb_session_oplock_break(
292 			    SMB_OFILE_GET_SESSION(ol->ol_ofile), ol->ol_ofile);
293 			mutex_enter(&node->n_mutex);
294 			continue;
295 		}
296 
297 		ASSERT(node->n_state == SMB_NODE_STATE_OPLOCK_BREAKING);
298 		if (nowait) {
299 			mutex_exit(&node->n_mutex);
300 			return (B_FALSE);
301 		}
302 		rc = cv_timedwait(&ol->ol_cv, &node->n_mutex, time);
303 		if (rc == -1) {
304 			/*
305 			 * Oplock release timed out.
306 			 */
307 			if (node->n_state == SMB_NODE_STATE_OPLOCK_BREAKING) {
308 				node->n_state = SMB_NODE_STATE_AVAILABLE;
309 				ol->ol_xthread = curthread;
310 				mutex_exit(&node->n_mutex);
311 				smb_fsop_oplock_uninstall(node);
312 				mutex_enter(&node->n_mutex);
313 				ol->ol_xthread = NULL;
314 				cv_broadcast(&ol->ol_cv);
315 				break;
316 			}
317 		}
318 	}
319 	mutex_exit(&node->n_mutex);
320 	return (B_TRUE);
321 }
322 
323 /*
324  * smb_oplock_release
325  *
326  * This function releases the oplock on the node passed in. If other threads
327  * were waiting for the oplock to be released they are signaled.
328  */
329 void
330 smb_oplock_release(smb_node_t *node, smb_ofile_t *of)
331 {
332 	smb_oplock_t	*ol;
333 
334 	SMB_NODE_VALID(node);
335 	ol = &node->n_oplock;
336 
337 	mutex_enter(&node->n_mutex);
338 	smb_oplock_wait(node);
339 	switch (node->n_state) {
340 	case SMB_NODE_STATE_AVAILABLE:
341 		break;
342 
343 	case SMB_NODE_STATE_OPLOCK_GRANTED:
344 	case SMB_NODE_STATE_OPLOCK_BREAKING:
345 		if (ol->ol_ofile == of) {
346 			node->n_state = SMB_NODE_STATE_AVAILABLE;
347 			ol->ol_xthread = curthread;
348 			mutex_exit(&node->n_mutex);
349 			smb_fsop_oplock_uninstall(node);
350 			mutex_enter(&node->n_mutex);
351 			ol->ol_xthread = NULL;
352 			cv_broadcast(&ol->ol_cv);
353 		}
354 		break;
355 
356 	default:
357 		SMB_PANIC();
358 	}
359 	mutex_exit(&node->n_mutex);
360 }
361 
362 /*
363  * smb_oplock_conflict
364  *
365  * The two checks on "session" and "op" are primarily for the open path.
366  * Other SMB functions may call smb_oplock_conflict() with a session
367  * pointer so as to do the session check.
368  */
369 boolean_t
370 smb_oplock_conflict(smb_node_t *node, smb_session_t *session,
371     smb_arg_open_t *op)
372 {
373 	boolean_t	rb;
374 
375 	SMB_NODE_VALID(node);
376 	SMB_SESSION_VALID(session);
377 
378 	mutex_enter(&node->n_mutex);
379 	smb_oplock_wait(node);
380 	switch (node->n_state) {
381 	case SMB_NODE_STATE_AVAILABLE:
382 		rb = B_FALSE;
383 		break;
384 
385 	case SMB_NODE_STATE_OPLOCK_GRANTED:
386 	case SMB_NODE_STATE_OPLOCK_BREAKING:
387 		if (SMB_SESSION_GET_ID(session) == node->n_oplock.ol_sess_id) {
388 			rb = B_FALSE;
389 			break;
390 		}
391 
392 		if (op != NULL) {
393 			if (((op->desired_access & ~(FILE_READ_ATTRIBUTES |
394 			    FILE_WRITE_ATTRIBUTES | SYNCHRONIZE)) == 0) &&
395 			    (op->create_disposition != FILE_SUPERSEDE) &&
396 			    (op->create_disposition != FILE_OVERWRITE)) {
397 				/* Attributs only */
398 				rb = B_FALSE;
399 				break;
400 			}
401 		}
402 		rb = B_TRUE;
403 		break;
404 
405 	default:
406 		SMB_PANIC();
407 	}
408 	mutex_exit(&node->n_mutex);
409 	return (rb);
410 }
411 
412 /*
413  * smb_oplock_broadcast
414  *
415  * The the calling thread has the pointer to its context stored in ol_thread
416  * it resets that field. If any other thread is waiting for that field to
417  * turn to NULL it is signaled.
418  *
419  * Returns:
420  *	B_TRUE	Oplock unlocked
421  *	B_FALSE	Oplock still locked
422  */
423 boolean_t
424 smb_oplock_broadcast(smb_node_t *node)
425 {
426 	smb_oplock_t	*ol;
427 	boolean_t	rb;
428 
429 	SMB_NODE_VALID(node);
430 	ol = &node->n_oplock;
431 	rb = B_FALSE;
432 
433 	mutex_enter(&node->n_mutex);
434 	if ((ol->ol_xthread != NULL) && (ol->ol_xthread == curthread)) {
435 		ol->ol_xthread = NULL;
436 		cv_broadcast(&ol->ol_cv);
437 		rb = B_TRUE;
438 	}
439 	mutex_exit(&node->n_mutex);
440 	return (rb);
441 }
442 
443 /*
444  * smb_oplock_wait
445  *
446  * The mutex of the node must have been entered before calling this function.
447  * If the field ol_xthread is not NULL and doesn't contain the pointer to the
448  * context of the calling thread, the caller will sleep until that field is
449  * reset (set to NULL).
450  */
451 static void
452 smb_oplock_wait(smb_node_t *node)
453 {
454 	smb_oplock_t	*ol = &node->n_oplock;
455 
456 	if ((ol->ol_xthread != NULL) && (ol->ol_xthread != curthread)) {
457 		ASSERT(!MUTEX_HELD(&ol->ol_ofile->f_mutex));
458 		while (ol->ol_xthread != NULL)
459 			cv_wait(&ol->ol_cv, &node->n_mutex);
460 	}
461 }
462