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