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