xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb_delete.c (revision 037cac007b685e7ea79f6ef7e8e62bfd342a4d56)
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 #include <smbsrv/smb_incl.h>
27 #include <smbsrv/smb_fsops.h>
28 #include <smbsrv/smbinfo.h>
29 #include <sys/nbmlock.h>
30 
31 static int smb_delete_check_path(smb_request_t *, boolean_t *);
32 static int smb_delete_single_file(smb_request_t *, smb_error_t *);
33 static int smb_delete_multiple_files(smb_request_t *, smb_error_t *);
34 static int smb_delete_find_fname(smb_request_t *, smb_odir_t *);
35 static int smb_delete_check_dosattr(smb_request_t *, smb_error_t *);
36 static int smb_delete_remove_file(smb_request_t *, smb_error_t *);
37 
38 static void smb_delete_error(smb_error_t *, uint32_t, uint16_t, uint16_t);
39 
40 /*
41  * smb_com_delete
42  *
43  * The delete file message is sent to delete a data file. The appropriate
44  * Tid and additional pathname are passed. Read only files may not be
45  * deleted, the read-only attribute must be reset prior to file deletion.
46  *
47  * NT supports a hidden permission known as File Delete Child (FDC). If
48  * the user has FullControl access to a directory, the user is permitted
49  * to delete any object in the directory regardless of the permissions
50  * on the object.
51  *
52  * Client Request                     Description
53  * ================================== =================================
54  * UCHAR WordCount;                   Count of parameter words = 1
55  * USHORT SearchAttributes;
56  * USHORT ByteCount;                  Count of data bytes; min = 2
57  * UCHAR BufferFormat;                0x04
58  * STRING FileName[];                 File name
59  *
60  * Multiple files may be deleted in response to a single request as
61  * SMB_COM_DELETE supports wildcards
62  *
63  * SearchAttributes indicates the attributes that the target file(s) must
64  * have. If the attribute is zero then only normal files are deleted. If
65  * the system file or hidden attributes are specified then the delete is
66  * inclusive -both the specified type(s) of files and normal files are
67  * deleted. Attributes are described in the "Attribute Encoding" section
68  * of this document.
69  *
70  * If bit0 of the Flags2 field of the SMB header is set, a pattern is
71  * passed in, and the file has a long name, then the passed pattern  much
72  * match the long file name for the delete to succeed. If bit0 is clear, a
73  * pattern is passed in, and the file has a long name, then the passed
74  * pattern must match the file's short name for the deletion to succeed.
75  *
76  * Server Response                    Description
77  * ================================== =================================
78  * UCHAR WordCount;                   Count of parameter words = 0
79  * USHORT ByteCount;                  Count of data bytes = 0
80  *
81  * 4.2.10.1  Errors
82  *
83  * ERRDOS/ERRbadpath
84  * ERRDOS/ERRbadfile
85  * ERRDOS/ERRnoaccess
86  * ERRDOS/ERRbadshare	# returned by NT for files that are already open
87  * ERRHRD/ERRnowrite
88  * ERRSRV/ERRaccess
89  * ERRSRV/ERRinvdevice
90  * ERRSRV/ERRinvid
91  * ERRSRV/ERRbaduid
92  */
93 smb_sdrc_t
94 smb_pre_delete(smb_request_t *sr)
95 {
96 	int rc;
97 	smb_fqi_t *fqi;
98 
99 	fqi = &sr->arg.dirop.fqi;
100 
101 	if ((rc = smbsr_decode_vwv(sr, "w", &fqi->fq_sattr)) == 0)
102 		rc = smbsr_decode_data(sr, "%S", sr, &fqi->fq_path.pn_path);
103 
104 	DTRACE_SMB_2(op__Delete__start, smb_request_t *, sr, smb_fqi_t *, fqi);
105 
106 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
107 }
108 
109 void
110 smb_post_delete(smb_request_t *sr)
111 {
112 	DTRACE_SMB_1(op__Delete__done, smb_request_t *, sr);
113 }
114 
115 /*
116  * smb_com_delete
117  *
118  * 1. pre-process pathname -  smb_delete_check_path()
119  *    checks dot, bad path syntax, wildcards in path
120  *
121  * 2. process the path to get directory node & last_comp,
122  *    store these in fqi
123  *    - If smb_pathname_reduce cannot find the specified path,
124  *      the error (ENOTDIR) is translated to NT_STATUS_OBJECT_PATH_NOT_FOUND
125  *      if the target is a single file (no wildcards).  If there are
126  *      wildcards in the last_comp, NT_STATUS_OBJECT_NAME_NOT_FOUND is
127  *      used instead.
128  *    - If the directory node is the mount point and the last component
129  *      is ".." NT_STATUS_OBJECT_PATH_SYNTAX_BAD is returned.
130  *
131  * 3. check access permissions
132  *
133  * 4. invoke the appropriate deletion routine to find and remove
134  *    the specified file(s).
135  *    - if target is a single file (no wildcards) - smb_delete_single_file
136  *    - if the target contains wildcards - smb_delete_multiple_files
137  *
138  * Returns: SDRC_SUCCESS or SDRC_ERROR
139  */
140 smb_sdrc_t
141 smb_com_delete(smb_request_t *sr)
142 {
143 	int rc;
144 	smb_error_t err;
145 	uint32_t status;
146 	boolean_t wildcards;
147 	smb_fqi_t *fqi;
148 
149 	fqi = &sr->arg.dirop.fqi;
150 
151 	if (smb_delete_check_path(sr, &wildcards) != 0)
152 		return (SDRC_ERROR);
153 
154 	rc = smb_pathname_reduce(sr, sr->user_cr, fqi->fq_path.pn_path,
155 	    sr->tid_tree->t_snode, sr->tid_tree->t_snode,
156 	    &fqi->fq_dnode, fqi->fq_last_comp);
157 	if (rc == 0) {
158 		if (fqi->fq_dnode->vp->v_type != VDIR) {
159 			smb_node_release(fqi->fq_dnode);
160 			rc = ENOTDIR;
161 		}
162 	}
163 	if (rc != 0) {
164 		if (rc == ENOTDIR) {
165 			if (wildcards)
166 				status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
167 			else
168 				status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
169 			smbsr_error(sr, status, ERRDOS, ERROR_FILE_NOT_FOUND);
170 		} else {
171 			smbsr_errno(sr, rc);
172 		}
173 
174 		return (SDRC_ERROR);
175 	}
176 
177 	if ((fqi->fq_dnode == sr->tid_tree->t_snode) &&
178 	    (strcmp(fqi->fq_last_comp, "..") == 0)) {
179 		smb_node_release(fqi->fq_dnode);
180 		smbsr_error(sr, NT_STATUS_OBJECT_PATH_SYNTAX_BAD,
181 		    ERRDOS, ERROR_BAD_PATHNAME);
182 		return (SDRC_ERROR);
183 	}
184 
185 	rc = smb_fsop_access(sr, sr->user_cr, fqi->fq_dnode,
186 	    FILE_LIST_DIRECTORY);
187 	if (rc != 0) {
188 		smb_node_release(fqi->fq_dnode);
189 		smbsr_error(sr, NT_STATUS_ACCESS_DENIED,
190 		    ERRDOS, ERROR_ACCESS_DENIED);
191 		return (SDRC_ERROR);
192 	}
193 
194 	if (wildcards)
195 		rc = smb_delete_multiple_files(sr, &err);
196 	else
197 		rc = smb_delete_single_file(sr, &err);
198 
199 	smb_node_release(fqi->fq_dnode);
200 
201 	if (rc != 0)
202 		smbsr_set_error(sr, &err);
203 	else
204 		rc = smbsr_encode_empty_result(sr);
205 
206 	return (rc == 0 ? SDRC_SUCCESS : SDRC_ERROR);
207 }
208 
209 /*
210  * smb_delete_single_file
211  *
212  * Find the specified file and, if its attributes match the search
213  * criteria, delete it.
214  *
215  * Returns 0 - success (file deleted)
216  *        -1 - error, err is populated with error details
217  */
218 static int
219 smb_delete_single_file(smb_request_t *sr, smb_error_t *err)
220 {
221 	smb_fqi_t *fqi;
222 	uint32_t status;
223 
224 	fqi = &sr->arg.dirop.fqi;
225 
226 	smb_pathname_setup(sr, &fqi->fq_path);
227 	status = smb_validate_object_name(&fqi->fq_path);
228 	if (status != NT_STATUS_SUCCESS) {
229 		smb_delete_error(err, status, ERRDOS, ERROR_INVALID_NAME);
230 		return (-1);
231 	}
232 
233 	if (smb_fsop_lookup_name(sr, sr->user_cr, 0, sr->tid_tree->t_snode,
234 	    fqi->fq_dnode, fqi->fq_last_comp, &fqi->fq_fnode) != 0) {
235 		smb_delete_error(err, NT_STATUS_OBJECT_NAME_NOT_FOUND,
236 		    ERRDOS, ERROR_FILE_NOT_FOUND);
237 		return (-1);
238 	}
239 
240 	if (smb_delete_check_dosattr(sr, err) != 0) {
241 		smb_node_release(fqi->fq_fnode);
242 		return (-1);
243 	}
244 
245 	if (smb_delete_remove_file(sr, err) != 0) {
246 		smb_node_release(fqi->fq_fnode);
247 		return (-1);
248 	}
249 
250 	smb_node_release(fqi->fq_fnode);
251 	return (0);
252 }
253 
254 /*
255  * smb_delete_multiple_files
256  *
257  * For each matching file found by smb_delete_find_fname:
258  * 1. lookup file
259  * 2. check the file's attributes
260  *    - The search ends with an error if a readonly file
261  *      (NT_STATUS_CANNOT_DELETE) is matched.
262  *    - The search ends (but not an error) if a directory is
263  *      matched and the request's search did not include
264  *      directories.
265  *    - Otherwise, if smb_delete_check_dosattr fails the file
266  *      is skipped and the search continues (at step 1)
267  * 3. delete the file
268  *
269  * Returns 0 - success
270  *        -1 - error, err is populated with error details
271  */
272 static int
273 smb_delete_multiple_files(smb_request_t *sr, smb_error_t *err)
274 {
275 	int rc, deleted = 0;
276 	smb_fqi_t *fqi;
277 	uint16_t odid;
278 	smb_odir_t *od;
279 
280 	fqi = &sr->arg.dirop.fqi;
281 
282 	/*
283 	 * Specify all search attributes (SMB_SEARCH_ATTRIBUTES) so that
284 	 * delete-specific checking can be done (smb_delete_check_dosattr).
285 	 */
286 	odid = smb_odir_open(sr, fqi->fq_path.pn_path,
287 	    SMB_SEARCH_ATTRIBUTES, 0);
288 	if (odid == 0)
289 		return (-1);
290 
291 	if ((od = smb_tree_lookup_odir(sr->tid_tree, odid)) == NULL)
292 		return (-1);
293 
294 	for (;;) {
295 		rc = smb_delete_find_fname(sr, od);
296 		if (rc != 0)
297 			break;
298 
299 		rc = smb_fsop_lookup_name(sr, sr->user_cr, 0,
300 		    sr->tid_tree->t_snode, fqi->fq_dnode,
301 		    fqi->fq_od_name, &fqi->fq_fnode);
302 		if (rc != 0)
303 			break;
304 
305 		if (smb_delete_check_dosattr(sr, err) != 0) {
306 			smb_node_release(fqi->fq_fnode);
307 			if (err->status == NT_STATUS_CANNOT_DELETE) {
308 				smb_odir_close(od);
309 				smb_odir_release(od);
310 				return (-1);
311 			}
312 			if ((err->status == NT_STATUS_FILE_IS_A_DIRECTORY) &&
313 			    (SMB_SEARCH_DIRECTORY(fqi->fq_sattr) != 0))
314 				break;
315 			continue;
316 		}
317 
318 		if (smb_delete_remove_file(sr, err) == 0) {
319 			++deleted;
320 			smb_node_release(fqi->fq_fnode);
321 			continue;
322 		}
323 		if (err->status == NT_STATUS_OBJECT_NAME_NOT_FOUND) {
324 			smb_node_release(fqi->fq_fnode);
325 			continue;
326 		}
327 
328 		smb_odir_close(od);
329 		smb_odir_release(od);
330 		smb_node_release(fqi->fq_fnode);
331 		return (-1);
332 	}
333 
334 	smb_odir_close(od);
335 	smb_odir_release(od);
336 
337 	if ((rc != 0) && (rc != ENOENT)) {
338 		smbsr_map_errno(rc, err);
339 		return (-1);
340 	}
341 
342 	if (deleted == 0) {
343 		smb_delete_error(err, NT_STATUS_NO_SUCH_FILE,
344 		    ERRDOS, ERROR_FILE_NOT_FOUND);
345 		return (-1);
346 	}
347 
348 	return (0);
349 }
350 
351 /*
352  * smb_delete_find_fname
353  *
354  * Find next filename that matches search pattern (fqi->fq_last_comp)
355  * and save it in fqi->fq_od_name.
356  *
357  * Case insensitivity note:
358  * If the tree is case insensitive and there's a case conflict
359  * with the name returned from smb_odir_read, smb_delete_find_fname
360  * performs case conflict name mangling to produce a unique filename.
361  * This ensures that any subsequent smb_fsop_lookup, (which will
362  * find the first case insensitive match) will find the correct file.
363  *
364  * Returns: 0 - success
365  *          errno
366  */
367 static int
368 smb_delete_find_fname(smb_request_t *sr, smb_odir_t *od)
369 {
370 	int		rc;
371 	smb_odirent_t	*odirent;
372 	boolean_t	eos;
373 	char		*name;
374 	char		shortname[SMB_SHORTNAMELEN];
375 	char		name83[SMB_SHORTNAMELEN];
376 	smb_fqi_t	*fqi;
377 
378 	fqi = &sr->arg.dirop.fqi;
379 	odirent = kmem_alloc(sizeof (smb_odirent_t), KM_SLEEP);
380 
381 	rc = smb_odir_read(sr, od, odirent, &eos);
382 	if (rc != 0) {
383 		kmem_free(odirent, sizeof (smb_odirent_t));
384 		return (rc);
385 	}
386 	if (eos) {
387 		kmem_free(odirent, sizeof (smb_odirent_t));
388 		return (ENOENT);
389 	}
390 
391 	/* if case conflict, force mangle and use shortname */
392 	if ((od->d_flags & SMB_ODIR_FLAG_IGNORE_CASE) &&
393 	    (odirent->od_eflags & ED_CASE_CONFLICT)) {
394 		(void) smb_mangle_name(odirent->od_ino, odirent->od_name,
395 		    shortname, name83, 1);
396 		name = shortname;
397 	} else {
398 		name = odirent->od_name;
399 	}
400 	(void) strlcpy(fqi->fq_od_name, name, sizeof (fqi->fq_od_name));
401 
402 	kmem_free(odirent, sizeof (smb_odirent_t));
403 	return (0);
404 }
405 
406 /*
407  * smb_delete_check_dosattr
408  *
409  * Check file's dos atributes to ensure that
410  * 1. the file is not a directory - NT_STATUS_FILE_IS_A_DIRECTORY
411  * 2. the file is not readonly - NT_STATUS_CANNOT_DELETE
412  * 3. the file's dos attributes comply with the specified search attributes
413  *     If the file is either hidden or system and those attributes
414  *     are not specified in the search attributes - NT_STATUS_NO_SUCH_FILE
415  *
416  * Returns: 0 - file's attributes pass all checks
417  *         -1 - err populated with error details
418  */
419 static int
420 smb_delete_check_dosattr(smb_request_t *sr, smb_error_t *err)
421 {
422 	smb_fqi_t *fqi;
423 	smb_node_t *node;
424 	smb_attr_t attr;
425 	uint16_t sattr;
426 
427 	fqi = &sr->arg.dirop.fqi;
428 	sattr = fqi->fq_sattr;
429 	node = fqi->fq_fnode;
430 
431 	if (smb_node_getattr(sr, node, &attr) != 0) {
432 		smb_delete_error(err, NT_STATUS_INTERNAL_ERROR,
433 		    ERRDOS, ERROR_INTERNAL_ERROR);
434 		return (-1);
435 	}
436 
437 	if (attr.sa_dosattr & FILE_ATTRIBUTE_DIRECTORY) {
438 		smb_delete_error(err, NT_STATUS_FILE_IS_A_DIRECTORY,
439 		    ERRDOS, ERROR_ACCESS_DENIED);
440 		return (-1);
441 	}
442 
443 	if (SMB_PATHFILE_IS_READONLY(sr, node)) {
444 		smb_delete_error(err, NT_STATUS_CANNOT_DELETE,
445 		    ERRDOS, ERROR_ACCESS_DENIED);
446 		return (-1);
447 	}
448 
449 	if ((attr.sa_dosattr & FILE_ATTRIBUTE_HIDDEN) &&
450 	    !(SMB_SEARCH_HIDDEN(sattr))) {
451 		smb_delete_error(err, NT_STATUS_NO_SUCH_FILE,
452 		    ERRDOS, ERROR_FILE_NOT_FOUND);
453 		return (-1);
454 	}
455 
456 	if ((attr.sa_dosattr & FILE_ATTRIBUTE_SYSTEM) &&
457 	    !(SMB_SEARCH_SYSTEM(sattr))) {
458 		smb_delete_error(err, NT_STATUS_NO_SUCH_FILE,
459 		    ERRDOS, ERROR_FILE_NOT_FOUND);
460 		return (-1);
461 	}
462 
463 	return (0);
464 }
465 
466 /*
467  * smb_delete_remove_file
468  *
469  * For consistency with Windows 2000, the range check should be done
470  * after checking for sharing violations.  Attempting to delete a
471  * locked file will result in sharing violation, which is the same
472  * thing that will happen if you try to delete a non-locked open file.
473  *
474  * Note that windows 2000 rejects lock requests on open files that
475  * have been opened with metadata open modes.  The error is
476  * STATUS_ACCESS_DENIED.
477  *
478  * NT does not always close a file immediately, which can cause the
479  * share and access checking to fail (the node refcnt is greater
480  * than one), and the file doesn't get deleted. Breaking the oplock
481  * before share and access checking gives the client a chance to
482  * close the file.
483  *
484  * Returns: 0 - success
485  *         -1 - error, err populated with error details
486  */
487 static int
488 smb_delete_remove_file(smb_request_t *sr, smb_error_t *err)
489 {
490 	int rc;
491 	uint32_t status;
492 	smb_fqi_t *fqi;
493 	smb_node_t *node;
494 	uint32_t flags = 0;
495 
496 	fqi = &sr->arg.dirop.fqi;
497 	node = fqi->fq_fnode;
498 
499 	(void) smb_oplock_break(node, sr->session, B_FALSE);
500 
501 	smb_node_start_crit(node, RW_READER);
502 
503 	status = smb_node_delete_check(node);
504 	if (status != NT_STATUS_SUCCESS) {
505 		smb_delete_error(err, NT_STATUS_SHARING_VIOLATION,
506 		    ERRDOS, ERROR_SHARING_VIOLATION);
507 		smb_node_end_crit(node);
508 		return (-1);
509 	}
510 
511 	status = smb_range_check(sr, node, 0, UINT64_MAX, B_TRUE);
512 	if (status != NT_STATUS_SUCCESS) {
513 		smb_delete_error(err, NT_STATUS_ACCESS_DENIED,
514 		    ERRDOS, ERROR_ACCESS_DENIED);
515 		smb_node_end_crit(node);
516 		return (-1);
517 	}
518 
519 	if (SMB_TREE_SUPPORTS_CATIA(sr))
520 		flags |= SMB_CATIA;
521 
522 	rc = smb_fsop_remove(sr, sr->user_cr, node->dir_snode,
523 	    node->od_name, flags);
524 	if (rc != 0) {
525 		if (rc == ENOENT)
526 			smb_delete_error(err, NT_STATUS_OBJECT_NAME_NOT_FOUND,
527 			    ERRDOS, ERROR_FILE_NOT_FOUND);
528 		else
529 			smbsr_map_errno(rc, err);
530 
531 		smb_node_end_crit(node);
532 		return (-1);
533 	}
534 
535 	smb_node_end_crit(node);
536 	return (0);
537 }
538 
539 
540 /*
541  * smb_delete_check_path
542  *
543  * Perform initial validation on the pathname and last_comp.
544  *
545  * wildcards in path:
546  * Wildcards in the path (excluding the last_comp) should result
547  * in NT_STATUS_OBJECT_NAME_INVALID.
548  *
549  * bad path syntax:
550  * On unix .. at the root of a file system links to the root. Thus
551  * an attempt to lookup "/../../.." will be the same as looking up "/"
552  * CIFs clients expect the above to result in
553  * NT_STATUS_OBJECT_PATH_SYNTAX_BAD. It is currently not possible
554  * (and questionable if it's desirable) to deal with all cases
555  * but paths beginning with \\.. are handled. See bad_paths[].
556  * Cases like "\\dir\\..\\.." will be caught and handled after the
557  * pnreduce.  Cases like "\\dir\\..\\..\\filename" will still result
558  * in "\\filename" which is contrary to windows behavior.
559  *
560  * dot:
561  * A filename of '.' should result in NT_STATUS_OBJECT_NAME_INVALID
562  * Any wildcard filename that resolves to '.' should result in
563  * NT_STATUS_OBJECT_NAME_INVALID if the search attributes include
564  * FILE_ATTRIBUTE_DIRECTORY
565  *
566  * Returns:
567  *   0:  path is valid. Sets *wildcard to TRUE if wildcard delete
568  *	         i.e. if wildcards in last component
569  *  -1: path is invalid. Sets error information in sr.
570  */
571 static int
572 smb_delete_check_path(smb_request_t *sr, boolean_t *wildcard)
573 {
574 	smb_fqi_t *fqi = &sr->arg.dirop.fqi;
575 	char *p, *last_comp;
576 	int i, wildcards;
577 	smb_pathname_t *pn = &fqi->fq_path;
578 
579 	struct {
580 		char *name;
581 		int len;
582 	} *bad, bad_paths[] = {
583 		{"\\..\0", 4},
584 		{"\\..\\", 4},
585 		{"..\0", 3},
586 		{"..\\", 3}
587 	};
588 
589 	/* find last component, strip trailing '\\' */
590 	p = pn->pn_path + strlen(pn->pn_path) - 1;
591 	while (*p == '\\') {
592 		*p = '\0';
593 		--p;
594 	}
595 
596 	if ((p = strrchr(pn->pn_path, '\\')) == NULL)
597 		last_comp = pn->pn_path;
598 	else
599 		last_comp = ++p;
600 
601 	wildcards = smb_convert_wildcards(last_comp);
602 
603 	if (last_comp != pn->pn_path) {
604 		/*
605 		 * Wildcards are only allowed in the last component.
606 		 * Check for additional wildcards in the path.
607 		 */
608 		if (smb_convert_wildcards(pn->pn_path) != wildcards) {
609 			smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID,
610 			    ERRDOS, ERROR_INVALID_NAME);
611 			return (-1);
612 		}
613 	}
614 
615 	/* path above the mount point */
616 	for (i = 0; i < sizeof (bad_paths) / sizeof (bad_paths[0]); ++i) {
617 		bad = &bad_paths[i];
618 		if (strncmp(pn->pn_path, bad->name, bad->len) == 0) {
619 			smbsr_error(sr, NT_STATUS_OBJECT_PATH_SYNTAX_BAD,
620 			    ERRDOS, ERROR_BAD_PATHNAME);
621 			return (-1);
622 		}
623 	}
624 
625 	/* last component is, or resolves to, '.' (dot) */
626 	if ((strcmp(last_comp, ".") == 0) ||
627 	    (SMB_SEARCH_DIRECTORY(fqi->fq_sattr) &&
628 	    (smb_match(last_comp, ".")))) {
629 		smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID,
630 		    ERRDOS, ERROR_INVALID_NAME);
631 		return (-1);
632 	}
633 
634 	*wildcard = (wildcards != 0);
635 	return (0);
636 }
637 
638 /*
639  * smb_delete_error
640  */
641 static void
642 smb_delete_error(smb_error_t *err,
643     uint32_t status, uint16_t errcls, uint16_t errcode)
644 {
645 	err->severity = ERROR_SEVERITY_ERROR;
646 	err->status = status;
647 	err->errcls = errcls;
648 	err->errcode = errcode;
649 }
650