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