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
  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_1(op__Delete__start, smb_request_t *, sr); /* arg.dirop */
 108 
 109         return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
 110 }
 111 
 112 void
 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
 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
 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
 283 smb_delete_multiple_files(smb_request_t *sr, smb_error_t *err)
 284 {
 285         char namebuf[MAXNAMELEN];
 286         smb_fqi_t *fqi;
 287         smb_odir_t *od;
 288         uint32_t status;
 289         int rc, deleted = 0;
 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         status = smb_odir_openpath(sr, fqi->fq_path.pn_path,
 298             SMB_SEARCH_ATTRIBUTES, 0, &od);
 299         if (status != 0) {
 300                 err->status = status;
 301                 return (-1);
 302         }
 303 
 304         for (;;) {
 305                 rc = smb_delete_find_fname(sr, od, namebuf, MAXNAMELEN);
 306                 if (rc != 0)
 307                         break;
 308 
 309                 rc = smb_fsop_lookup_name(sr, sr->user_cr, SMB_CASE_SENSITIVE,
 310                     sr->tid_tree->t_snode, fqi->fq_dnode,
 311                     namebuf, &fqi->fq_fnode);
 312                 if (rc != 0)
 313                         break;
 314 
 315                 if (smb_delete_check_dosattr(sr, err) != 0) {
 316                         smb_node_release(fqi->fq_fnode);
 317                         if (err->status == NT_STATUS_CANNOT_DELETE) {
 318                                 smb_odir_close(od);
 319                                 smb_odir_release(od);
 320                                 return (-1);
 321                         }
 322                         if ((err->status == NT_STATUS_FILE_IS_A_DIRECTORY) &&
 323                             (SMB_SEARCH_DIRECTORY(fqi->fq_sattr) != 0))
 324                                 break;
 325                         continue;
 326                 }
 327 
 328                 if (smb_delete_remove_file(sr, err) == 0) {
 329                         ++deleted;
 330                         smb_node_release(fqi->fq_fnode);
 331                         continue;
 332                 }
 333                 if (err->status == NT_STATUS_OBJECT_NAME_NOT_FOUND) {
 334                         smb_node_release(fqi->fq_fnode);
 335                         continue;
 336                 }
 337 
 338                 smb_odir_close(od);
 339                 smb_odir_release(od);
 340                 smb_node_release(fqi->fq_fnode);
 341                 return (-1);
 342         }
 343 
 344         smb_odir_close(od);
 345         smb_odir_release(od);
 346 
 347         if ((rc != 0) && (rc != ENOENT)) {
 348                 smbsr_map_errno(rc, err);
 349                 return (-1);
 350         }
 351 
 352         if (deleted == 0) {
 353                 smb_delete_error(err, NT_STATUS_NO_SUCH_FILE,
 354                     ERRDOS, ERROR_FILE_NOT_FOUND);
 355                 return (-1);
 356         }
 357 
 358         return (0);
 359 }
 360 
 361 /*
 362  * smb_delete_find_fname
 363  *
 364  * Find next filename that matches search pattern and return it
 365  * in namebuf.
 366  *
 367  * Returns: 0 - success
 368  *          errno
 369  */
 370 static int
 371 smb_delete_find_fname(smb_request_t *sr, smb_odir_t *od, char *namebuf, int len)
 372 {
 373         int             rc;
 374         smb_odirent_t   *odirent;
 375         boolean_t       eos;
 376 
 377         odirent = kmem_alloc(sizeof (smb_odirent_t), KM_SLEEP);
 378 
 379         rc = smb_odir_read(sr, od, odirent, &eos);
 380         if (rc == 0) {
 381                 (void) strlcpy(namebuf, odirent->od_name, len);
 382         }
 383         kmem_free(odirent, sizeof (smb_odirent_t));
 384         return (rc);
 385 }
 386 
 387 /*
 388  * smb_delete_check_dosattr
 389  *
 390  * Check file's dos atributes to ensure that
 391  * 1. the file is not a directory - NT_STATUS_FILE_IS_A_DIRECTORY
 392  * 2. the file is not readonly - NT_STATUS_CANNOT_DELETE
 393  * 3. the file's dos attributes comply with the specified search attributes
 394  *     If the file is either hidden or system and those attributes
 395  *     are not specified in the search attributes - NT_STATUS_NO_SUCH_FILE
 396  *
 397  * Returns: 0 - file's attributes pass all checks
 398  *         -1 - err populated with error details
 399  */
 400 static int
 401 smb_delete_check_dosattr(smb_request_t *sr, smb_error_t *err)
 402 {
 403         smb_fqi_t *fqi;
 404         smb_node_t *node;
 405         smb_attr_t attr;
 406         uint16_t sattr;
 407 
 408         fqi = &sr->arg.dirop.fqi;
 409         sattr = fqi->fq_sattr;
 410         node = fqi->fq_fnode;
 411 
 412         bzero(&attr, sizeof (attr));
 413         attr.sa_mask = SMB_AT_DOSATTR;
 414         if (smb_node_getattr(sr, node, zone_kcred(), NULL, &attr) != 0) {
 415                 smb_delete_error(err, NT_STATUS_INTERNAL_ERROR,
 416                     ERRDOS, ERROR_INTERNAL_ERROR);
 417                 return (-1);
 418         }
 419 
 420         if (attr.sa_dosattr & FILE_ATTRIBUTE_DIRECTORY) {
 421                 smb_delete_error(err, NT_STATUS_FILE_IS_A_DIRECTORY,
 422                     ERRDOS, ERROR_ACCESS_DENIED);
 423                 return (-1);
 424         }
 425 
 426         if (SMB_PATHFILE_IS_READONLY(sr, node)) {
 427                 smb_delete_error(err, NT_STATUS_CANNOT_DELETE,
 428                     ERRDOS, ERROR_ACCESS_DENIED);
 429                 return (-1);
 430         }
 431 
 432         if ((attr.sa_dosattr & FILE_ATTRIBUTE_HIDDEN) &&
 433             !(SMB_SEARCH_HIDDEN(sattr))) {
 434                 smb_delete_error(err, NT_STATUS_NO_SUCH_FILE,
 435                     ERRDOS, ERROR_FILE_NOT_FOUND);
 436                 return (-1);
 437         }
 438 
 439         if ((attr.sa_dosattr & FILE_ATTRIBUTE_SYSTEM) &&
 440             !(SMB_SEARCH_SYSTEM(sattr))) {
 441                 smb_delete_error(err, NT_STATUS_NO_SUCH_FILE,
 442                     ERRDOS, ERROR_FILE_NOT_FOUND);
 443                 return (-1);
 444         }
 445 
 446         return (0);
 447 }
 448 
 449 /*
 450  * smb_delete_remove_file
 451  *
 452  * For consistency with Windows 2000, the range check should be done
 453  * after checking for sharing violations.  Attempting to delete a
 454  * locked file will result in sharing violation, which is the same
 455  * thing that will happen if you try to delete a non-locked open file.
 456  *
 457  * Note that windows 2000 rejects lock requests on open files that
 458  * have been opened with metadata open modes.  The error is
 459  * STATUS_ACCESS_DENIED.
 460  *
 461  * NT does not always close a file immediately, which can cause the
 462  * share and access checking to fail (the node refcnt is greater
 463  * than one), and the file doesn't get deleted. Breaking the oplock
 464  * before share and lock checking gives the client a chance to
 465  * close the file.
 466  *
 467  * Returns: 0 - success
 468  *         -1 - error, err populated with error details
 469  */
 470 static int
 471 smb_delete_remove_file(smb_request_t *sr, smb_error_t *err)
 472 {
 473         int rc, count;
 474         uint32_t status;
 475         smb_fqi_t *fqi;
 476         smb_node_t *node;
 477         uint32_t flags = 0;
 478 
 479         fqi = &sr->arg.dirop.fqi;
 480         node = fqi->fq_fnode;
 481 
 482         /*
 483          * Break BATCH oplock before ofile checks. If a client
 484          * has a file open, this will force a flush or close,
 485          * which may affect the outcome of any share checking.
 486          */
 487         (void) smb_oplock_break(sr, node,
 488             SMB_OPLOCK_BREAK_TO_LEVEL_II | SMB_OPLOCK_BREAK_BATCH);
 489 
 490         /*
 491          * Wait (a little) for the oplock break to be
 492          * responded to by clients closing handles.
 493          * Hold node->n_lock as reader to keep new
 494          * ofiles from showing up after we check.
 495          */
 496         smb_node_rdlock(node);
 497         for (count = 0; count <= 12; count++) {
 498                 status = smb_node_delete_check(node);
 499                 if (status != NT_STATUS_SHARING_VIOLATION)
 500                         break;
 501                 smb_node_unlock(node);
 502                 delay(MSEC_TO_TICK(100));
 503                 smb_node_rdlock(node);
 504         }
 505         if (status != NT_STATUS_SUCCESS) {
 506                 smb_delete_error(err, NT_STATUS_SHARING_VIOLATION,
 507                     ERRDOS, ERROR_SHARING_VIOLATION);
 508                 smb_node_unlock(node);
 509                 return (-1);
 510         }
 511 
 512         /*
 513          * Note, the combination of these two:
 514          *      smb_node_rdlock(node);
 515          *      nbl_start_crit(node->vp, RW_READER);
 516          * is equivalent to this call:
 517          *      smb_node_start_crit(node, RW_READER)
 518          *
 519          * Cleanup after this point should use:
 520          *      smb_node_end_crit(node)
 521          */
 522         nbl_start_crit(node->vp, RW_READER);
 523 
 524         /*
 525          * This checks nbl_share_conflict, nbl_lock_conflict
 526          */
 527         status = smb_nbl_conflict(node, 0, UINT64_MAX, NBL_REMOVE);
 528         if (status == NT_STATUS_SHARING_VIOLATION) {
 529                 smb_node_end_crit(node);
 530                 smb_delete_error(err, NT_STATUS_SHARING_VIOLATION,
 531                     ERRDOS, ERROR_SHARING_VIOLATION);
 532                 return (-1);
 533         }
 534         if (status != NT_STATUS_SUCCESS) {
 535                 smb_node_end_crit(node);
 536                 smb_delete_error(err, NT_STATUS_ACCESS_DENIED,
 537                     ERRDOS, ERROR_ACCESS_DENIED);
 538                 return (-1);
 539         }
 540 
 541         if (SMB_TREE_SUPPORTS_CATIA(sr))
 542                 flags |= SMB_CATIA;
 543 
 544         rc = smb_fsop_remove(sr, sr->user_cr, node->n_dnode,
 545             node->od_name, flags);
 546         if (rc != 0) {
 547                 if (rc == ENOENT)
 548                         smb_delete_error(err, NT_STATUS_OBJECT_NAME_NOT_FOUND,
 549                             ERRDOS, ERROR_FILE_NOT_FOUND);
 550                 else
 551                         smbsr_map_errno(rc, err);
 552 
 553                 smb_node_end_crit(node);
 554                 return (-1);
 555         }
 556 
 557         smb_node_end_crit(node);
 558         return (0);
 559 }
 560 
 561 
 562 /*
 563  * smb_delete_check_path
 564  *
 565  * smb_pathname_validate() should already have been used to
 566  * perform initial validation on the pathname. Additional
 567  * request specific validation of the filename is performed
 568  * here.
 569  *
 570  * - pn->pn_fname is NULL should result in NT_STATUS_FILE_IS_A_DIRECTORY
 571  *
 572  * - Any wildcard filename that resolves to '.' should result in
 573  *   NT_STATUS_OBJECT_NAME_INVALID if the search attributes include
 574  *   FILE_ATTRIBUTE_DIRECTORY
 575  *
 576  * Returns:
 577  *   0: path is valid.
 578  *  -1: path is invalid. Sets error information in sr.
 579  */
 580 static int
 581 smb_delete_check_path(smb_request_t *sr)
 582 {
 583         smb_fqi_t *fqi = &sr->arg.dirop.fqi;
 584         smb_pathname_t *pn = &fqi->fq_path;
 585 
 586         if (pn->pn_fname == NULL) {
 587                 smbsr_error(sr, NT_STATUS_FILE_IS_A_DIRECTORY,
 588                     ERRDOS, ERROR_ACCESS_DENIED);
 589                 return (-1);
 590         }
 591 
 592         /* fname component is, or resolves to, '.' (dot) */
 593         if ((strcmp(pn->pn_fname, ".") == 0) ||
 594             (SMB_SEARCH_DIRECTORY(fqi->fq_sattr) &&
 595             (smb_match(pn->pn_fname, ".", B_FALSE)))) {
 596                 smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID,
 597                     ERRDOS, ERROR_INVALID_NAME);
 598                 return (-1);
 599         }
 600 
 601         return (0);
 602 }
 603 
 604 /*
 605  * smb_delete_error
 606  */
 607 static void
 608 smb_delete_error(smb_error_t *err,
 609     uint32_t status, uint16_t errcls, uint16_t errcode)
 610 {
 611         err->status = status;
 612         err->errcls = errcls;
 613         err->errcode = errcode;
 614 }