smb_oplock.c revision 12508:edb7861a1533
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
43static 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 */
142void
143smb_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 */
258boolean_t
259smb_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 */
329void
330smb_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 */
369boolean_t
370smb_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 */
423boolean_t
424smb_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 */
451static void
452smb_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