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 2008 Sun Microsystems, Inc.  All rights reserved.
  24  * Use is subject to license terms.
  25  * Copyright 2016 Joyent, Inc.
  26  */
  27 
  28 #include <sys/param.h>
  29 #include <sys/errno.h>
  30 #include <sys/systm.h>
  31 #include <sys/sysmacros.h>
  32 #include <sys/buf.h>
  33 #include <sys/vfs.h>
  34 #include <sys/kmem.h>
  35 #include <sys/vnode.h>
  36 #include <sys/debug.h>
  37 #include <sys/cmn_err.h>
  38 #include <sys/sunddi.h>
  39 #include <sys/fs/pc_label.h>
  40 #include <sys/fs/pc_fs.h>
  41 #include <sys/fs/pc_dir.h>
  42 #include <sys/fs/pc_node.h>
  43 
  44 static int pc_makedirentry(struct pcnode *dp, struct pcdir *direntries,
  45     int ndirentries, struct vattr *vap, offset_t offset);
  46 static int pc_dirempty(struct pcnode *);
  47 static int pc_findentry(struct pcnode *, char *, struct pcslot *, offset_t *);
  48 static int pc_parsename(char *, char *, char *);
  49 static int pc_remove_long_fn(struct pcnode *pcp,
  50     offset_t lfn_offset);
  51 static int generate_short_name(struct pcnode *dp, char *namep,
  52     struct pcdir *ep);
  53 static struct pcdir *pc_name_to_pcdir(struct pcnode *dp, char *namep,
  54     int ndirentries, int *errret);
  55 static offset_t pc_find_free_space(struct pcnode *pcp, int ndirentries);
  56 static int direntries_needed(struct pcnode *dp, char *namep);
  57 static int pc_is_short_file_name(char *namep, int foldcase);
  58 static int shortname_exists(struct pcnode *dp, char *fname, char *fext);
  59 static int pc_dirfixdotdot(struct pcnode *cdp, struct pcnode *opdp,
  60     struct pcnode *npdp);
  61 /*
  62  * Tunables
  63  */
  64 int enable_long_filenames = 1;
  65 
  66 /*
  67  * Lookup a name in a directory. Return a pointer to the pc_node
  68  * which represents the entry.
  69  */
  70 int
  71 pc_dirlook(
  72         struct pcnode *dp,              /* parent directory */
  73         char *namep,                    /* name to lookup */
  74         struct pcnode **pcpp)           /* result */
  75 {
  76         struct vnode *vp;
  77         struct pcslot slot;
  78         int error;
  79 
  80         PC_DPRINTF2(4, "pc_dirlook (dp %p name %s)\n", (void *)dp, namep);
  81 
  82         if (!(dp->pc_entry.pcd_attr & PCA_DIR)) {
  83                 return (ENOTDIR);
  84         }
  85         vp = PCTOV(dp);
  86         /*
  87          * check now for changed disk, before any return(0)
  88          */
  89         if (error = pc_verify(VFSTOPCFS(vp->v_vfsp)))
  90                 return (error);
  91 
  92         /*
  93          * Null component name is synonym for directory being searched.
  94          */
  95         if (*namep == '\0') {
  96                 VN_HOLD(vp);
  97                 *pcpp = dp;
  98                 return (0);
  99         }
 100         /*
 101          * The root directory does not have "." and ".." entries,
 102          * so they are faked here.
 103          */
 104         if (vp->v_flag & VROOT) {
 105                 if (bcmp(namep, ".", 2) == 0 || bcmp(namep, "..", 3) == 0) {
 106                         VN_HOLD(vp);
 107                         *pcpp = dp;
 108                         return (0);
 109                 }
 110         }
 111         error = pc_findentry(dp, namep, &slot, NULL);
 112         if (error == 0) {
 113                 *pcpp = pc_getnode(VFSTOPCFS(vp->v_vfsp),
 114                     slot.sl_blkno, slot.sl_offset, slot.sl_ep);
 115                 brelse(slot.sl_bp);
 116                 PC_DPRINTF1(4, "pc_dirlook: FOUND pcp=%p\n", (void *)*pcpp);
 117         } else if (error == EINVAL) {
 118                 error = ENOENT;
 119         }
 120         return (error);
 121 }
 122 
 123 /*
 124  * Enter a name in a directory.
 125  */
 126 int
 127 pc_direnter(
 128         struct pcnode *dp,              /* directory to make entry in */
 129         char *namep,                    /* name of entry */
 130         struct vattr *vap,              /* attributes of new entry */
 131         struct pcnode **pcpp)
 132 {
 133         int error;
 134         struct pcslot slot;
 135         struct vnode *vp = PCTOV(dp);
 136         struct pcfs *fsp = VFSTOPCFS(vp->v_vfsp);
 137         offset_t offset;
 138         daddr_t blkno;
 139         int     boff;
 140         struct buf *bp = NULL;
 141         struct pcdir *ep;
 142 
 143         PC_DPRINTF4(4, "pc_dirent(dp %p, name %s, vap %p, pcpp %p\n",
 144             (void *)dp, namep, (void *)vap, (void *)pcpp);
 145 
 146         if (pcpp != NULL)
 147                 *pcpp = NULL;
 148         /*
 149          * Leading spaces are not allowed in DOS.
 150          */
 151         if (*namep == ' ')
 152                 return (EINVAL);
 153         /*
 154          * If name is "." or "..", just look it up.
 155          */
 156         if (PC_NAME_IS_DOT(namep) || PC_NAME_IS_DOTDOT(namep)) {
 157                 if (pcpp) {
 158                         error = pc_dirlook(dp, namep, pcpp);
 159                         if (error)
 160                                 return (error);
 161                 }
 162                 return (EEXIST);
 163         }
 164         if (PCA_IS_HIDDEN(fsp, dp->pc_entry.pcd_attr)) {
 165                 return (EPERM);
 166         }
 167         /*
 168          * Make sure directory has not been removed while fs was unlocked.
 169          */
 170         if (dp->pc_entry.pcd_filename[0] == PCD_ERASED) {
 171                 return (ENOENT);
 172         }
 173         error = pc_findentry(dp, namep, &slot, NULL);
 174         if (error == 0) {
 175                 if (pcpp) {
 176                         *pcpp =
 177                             pc_getnode(fsp, slot.sl_blkno, slot.sl_offset,
 178                             slot.sl_ep);
 179                         error = EEXIST;
 180                 }
 181                 brelse(slot.sl_bp);
 182         } else if (error == ENOENT) {
 183                 struct pcdir *direntries;
 184                 int     ndirentries;
 185 
 186                 /*
 187                  * The entry does not exist. Check write permission in
 188                  * directory to see if entry can be created.
 189                  */
 190                 if (dp->pc_entry.pcd_attr & PCA_RDONLY) {
 191                         return (EPERM);
 192                 }
 193                 error = 0;
 194                 /*
 195                  * Make sure there is a slot.
 196                  */
 197                 if (slot.sl_status == SL_NONE)
 198                         panic("pc_direnter: no slot\n");
 199                 ndirentries = direntries_needed(dp, namep);
 200                 if (ndirentries == -1) {
 201                         return (EINVAL);
 202                 }
 203 
 204                 offset = pc_find_free_space(dp, ndirentries);
 205                 if (offset == -1) {
 206                         return (ENOSPC);
 207                 }
 208 
 209                 /*
 210                  * Make an entry from the supplied attributes.
 211                  */
 212                 direntries = pc_name_to_pcdir(dp, namep, ndirentries, &error);
 213                 if (direntries == NULL) {
 214                         return (error);
 215                 }
 216                 error = pc_makedirentry(dp, direntries, ndirentries, vap,
 217                     offset);
 218                 kmem_free(direntries, ndirentries * sizeof (struct pcdir));
 219                 if (error) {
 220                         return (error);
 221                 }
 222                 offset += (ndirentries - 1)  * sizeof (struct pcdir);
 223                 boff = pc_blkoff(fsp, offset);
 224                 error = pc_blkatoff(dp, offset, &bp, &ep);
 225                 if (error) {
 226                         return (error);
 227                 }
 228                 blkno = pc_daddrdb(fsp, bp->b_blkno);
 229                 /*
 230                  * Get a pcnode for the new entry.
 231                  */
 232                 *pcpp = pc_getnode(fsp, blkno, boff, ep);
 233                 brelse(bp);
 234                 if (vap->va_type == VDIR)
 235                         (*pcpp)->pc_size = fsp->pcfs_clsize;
 236 
 237                 /*
 238                  * Write out the new entry in the parent directory.
 239                  */
 240                 error = pc_syncfat(fsp);
 241                 if (!error) {
 242                         error = pc_nodeupdate(*pcpp);
 243                 }
 244         }
 245         return (error);
 246 }
 247 
 248 /*
 249  * Template for "." and ".." directory entries.
 250  */
 251 static struct {
 252         struct pcdir t_dot;             /* dot entry */
 253         struct pcdir t_dotdot;          /* dotdot entry */
 254 } dirtemplate = {
 255         {
 256                 ".       ",
 257                 "   ",
 258                 PCA_DIR
 259         },
 260         {
 261                 "..      ",
 262                 "   ",
 263                 PCA_DIR
 264         }
 265 };
 266 
 267 /*
 268  * Convert an attributes structure into the short filename entry
 269  * and write out the whole entry.
 270  */
 271 static int
 272 pc_makedirentry(struct pcnode *dp, struct pcdir *direntries,
 273     int ndirentries, struct vattr *vap, offset_t offset)
 274 {
 275         struct vnode *vp = PCTOV(dp);
 276         struct pcfs *fsp = VFSTOPCFS(vp->v_vfsp);
 277         int error;
 278         struct pcdir *ep;
 279         int     boff;
 280         int     i;
 281         struct buf *bp = NULL;
 282         timestruc_t now;
 283 
 284         if (vap != NULL && vap->va_mask & (AT_ATIME|AT_MTIME))
 285                 return (EOPNOTSUPP);
 286 
 287         ep = &direntries[ndirentries - 1];
 288         gethrestime(&now);
 289         if (error = pc_tvtopct(&now, &ep->pcd_mtime))
 290                 return (error);
 291 
 292         ep->pcd_crtime = ep->pcd_mtime;
 293         ep->pcd_ladate = ep->pcd_mtime.pct_date;
 294         ep->pcd_crtime_msec = 0;
 295         ep->pcd_size = 0;
 296         ep->pcd_attr = 0;
 297         /*
 298          * Fields we don't use.
 299          */
 300         ep->pcd_ntattr = 0;
 301         if (!IS_FAT32(fsp))
 302                 ep->un.pcd_eattr = 0;
 303 
 304         if (vap && ((vap->va_mode & 0222) == 0))
 305                 ep->pcd_attr |=  PCA_RDONLY;
 306         if (vap && (vap->va_type == VDIR)) {
 307                 pc_cluster32_t cn;
 308 
 309                 ep->pcd_attr |= PCA_DIR;
 310                 /*
 311                  * Make dot and dotdot entries for a new directory.
 312                  */
 313                 cn = pc_alloccluster(fsp, 0);
 314                 switch (cn) {
 315                 case PCF_FREECLUSTER:
 316                         return (ENOSPC);
 317                 case PCF_ERRORCLUSTER:
 318                         return (EIO);
 319                 }
 320                 bp = ngeteblk(fsp->pcfs_clsize);
 321                 bp->b_edev = fsp->pcfs_xdev;
 322                 bp->b_dev = cmpdev(bp->b_edev);
 323                 bp->b_blkno = pc_cldaddr(fsp, cn);
 324                 clrbuf(bp);
 325                 pc_setstartcluster(fsp, ep, cn);
 326                 pc_setstartcluster(fsp, &dirtemplate.t_dot, cn);
 327                 cn = pc_getstartcluster(fsp, &dp->pc_entry);
 328                 pc_setstartcluster(fsp, &dirtemplate.t_dotdot, cn);
 329                 dirtemplate.t_dot.pcd_mtime =
 330                     dirtemplate.t_dotdot.pcd_mtime = ep->pcd_mtime;
 331                 dirtemplate.t_dot.pcd_crtime =
 332                     dirtemplate.t_dotdot.pcd_crtime = ep->pcd_crtime;
 333                 dirtemplate.t_dot.pcd_ladate =
 334                     dirtemplate.t_dotdot.pcd_ladate = ep->pcd_ladate;
 335                 dirtemplate.t_dot.pcd_crtime_msec =
 336                     dirtemplate.t_dotdot.pcd_crtime_msec = 0;
 337                 bcopy(&dirtemplate,
 338                     bp->b_un.b_addr, sizeof (dirtemplate));
 339                 bwrite2(bp);
 340                 error = geterror(bp);
 341                 brelse(bp);
 342                 if (error) {
 343                         PC_DPRINTF0(1, "pc_makedirentry error");
 344                         pc_mark_irrecov(fsp);
 345                         return (EIO);
 346                 }
 347         } else {
 348                 pc_setstartcluster(fsp, ep, 0);
 349         }
 350         bp = NULL;
 351         for (i = 0, ep = NULL; i < ndirentries; i++, ep++) {
 352                 boff = pc_blkoff(fsp, offset);
 353                 if (boff == 0 || bp == NULL || boff >= bp->b_bcount) {
 354                         if (bp != NULL) {
 355                                 /* always modified */
 356                                 bwrite2(bp);
 357                                 error = geterror(bp);
 358                                 brelse(bp);
 359                                 if (error)
 360                                         return (error);
 361                                 bp = NULL;
 362                         }
 363                         error = pc_blkatoff(dp, offset, &bp, &ep);
 364                         if (error)
 365                                 return (error);
 366                 }
 367 
 368                 *ep = direntries[i];
 369                 offset += sizeof (struct pcdir);
 370         }
 371         if (bp != NULL) {
 372                 /* always modified */
 373                 bwrite2(bp);
 374                 error = geterror(bp);
 375                 brelse(bp);
 376                 if (error)
 377                         return (error);
 378         }
 379         return (0);
 380 }
 381 
 382 /*
 383  * Remove a name from a directory.
 384  */
 385 int
 386 pc_dirremove(
 387         struct pcnode *dp,
 388         char *namep,
 389         struct vnode *cdir,
 390         enum vtype type,
 391         caller_context_t *ctp)
 392 {
 393         struct pcslot slot;
 394         struct pcnode *pcp;
 395         int error;
 396         struct vnode *vp = PCTOV(dp);
 397         struct pcfs *fsp = VFSTOPCFS(vp->v_vfsp);
 398         offset_t lfn_offset = -1;
 399 
 400         PC_DPRINTF2(4, "pc_dirremove (dp %p name %s)\n", (void *)dp, namep);
 401         if ((dp->pc_entry.pcd_attr & PCA_RDONLY) ||
 402             PCA_IS_HIDDEN(fsp, dp->pc_entry.pcd_attr)) {
 403                 return (EPERM);
 404         }
 405         error = pc_findentry(dp, namep, &slot, &lfn_offset);
 406         if (error)
 407                 return (error);
 408         if (slot.sl_flags == SL_DOT) {
 409                 error = EINVAL;
 410         } else if (slot.sl_flags == SL_DOTDOT) {
 411                 error = ENOTEMPTY;
 412         } else {
 413                 pcp =
 414                     pc_getnode(VFSTOPCFS(vp->v_vfsp),
 415                     slot.sl_blkno, slot.sl_offset, slot.sl_ep);
 416         }
 417         if (error) {
 418                 brelse(slot.sl_bp);
 419                 return (error);
 420         }
 421         if (type == VDIR) {
 422                 if (pcp->pc_entry.pcd_attr & PCA_DIR) {
 423                         if (PCTOV(pcp) == cdir)
 424                                 error = EINVAL;
 425                         else if (!pc_dirempty(pcp))
 426                                 error = ENOTEMPTY;
 427                 } else {
 428                         error = ENOTDIR;
 429                 }
 430         } else {
 431                 if (pcp->pc_entry.pcd_attr & PCA_DIR)
 432                         error = EISDIR;
 433         }
 434         if (error == 0) {
 435                 /*
 436                  * Mark the in core node and on disk entry
 437                  * as removed. The slot may then be reused.
 438                  * The files clusters will be deallocated
 439                  * when the last reference goes away.
 440                  */
 441                 pcp->pc_eblkno = -1;
 442                 pcp->pc_entry.pcd_filename[0] = PCD_ERASED;
 443                 if (lfn_offset != -1) {
 444                         brelse(slot.sl_bp);
 445                         error = pc_remove_long_fn(dp, lfn_offset);
 446                         if (error) {
 447                                 VN_RELE(PCTOV(pcp));
 448                                 pc_mark_irrecov(VFSTOPCFS(vp->v_vfsp));
 449                                 return (EIO);
 450                         }
 451                 } else {
 452                         slot.sl_ep->pcd_filename[0] = PCD_ERASED;
 453                         bwrite2(slot.sl_bp);
 454                         error = geterror(slot.sl_bp);
 455                         brelse(slot.sl_bp);
 456                 }
 457                 if (error) {
 458                         VN_RELE(PCTOV(pcp));
 459                         pc_mark_irrecov(VFSTOPCFS(vp->v_vfsp));
 460                         return (EIO);
 461                 } else if (type == VDIR) {
 462                         error = pc_truncate(pcp, 0L);
 463                 }
 464 
 465         } else {
 466                 brelse(slot.sl_bp);
 467         }
 468 
 469         if (error == 0) {
 470                 if (type == VDIR) {
 471                         vnevent_rmdir(PCTOV(pcp), vp, namep, ctp);
 472                 } else {
 473                         vnevent_remove(PCTOV(pcp), vp, namep, ctp);
 474                 }
 475         }
 476 
 477         VN_RELE(PCTOV(pcp));
 478 
 479         return (error);
 480 }
 481 
 482 /*
 483  * Determine whether a directory is empty.
 484  */
 485 static int
 486 pc_dirempty(struct pcnode *pcp)
 487 {
 488         struct buf *bp;
 489         struct pcdir *ep;
 490         offset_t offset;
 491         int boff;
 492         char c;
 493         int error;
 494         struct vnode *vp;
 495 
 496         vp = PCTOV(pcp);
 497         bp = NULL;
 498 
 499         offset = 0;
 500         for (;;) {
 501 
 502                 /*
 503                  * If offset is on a block boundary,
 504                  * read in the next directory block.
 505                  * Release previous if it exists.
 506                  */
 507                 boff = pc_blkoff(VFSTOPCFS(vp->v_vfsp), offset);
 508                 if (boff == 0 || bp == NULL || boff >= bp->b_bcount) {
 509                         if (bp != NULL)
 510                                 brelse(bp);
 511                         if (error = pc_blkatoff(pcp, offset, &bp, &ep)) {
 512                                 return (error);
 513                         }
 514                 }
 515                 if (PCDL_IS_LFN(ep)) {
 516                         error = pc_extract_long_fn(pcp, NULL, &ep, &offset,
 517                             &bp);
 518                         /*
 519                          * EINVAL means the lfn was invalid, so start with
 520                          * the next entry. Otherwise, an error occurred _or_
 521                          * the lfn is valid, either of which means the
 522                          * directory is not empty.
 523                          */
 524                         if (error == EINVAL)
 525                                 continue;
 526                         else {
 527                                 if (bp)
 528                                         brelse(bp);
 529                                 return (error);
 530                         }
 531                 }
 532                 c = ep->pcd_filename[0];
 533                 if (c == PCD_UNUSED)
 534                         break;
 535                 if ((c != '.') && (c != PCD_ERASED)) {
 536                         brelse(bp);
 537                         return (0);
 538                 }
 539                 if ((c == '.') && !PC_SHORTNAME_IS_DOT(ep->pcd_filename) &&
 540                     !PC_SHORTNAME_IS_DOTDOT(ep->pcd_filename)) {
 541                         brelse(bp);
 542                         return (0);
 543                 }
 544                 ep++;
 545                 offset += sizeof (struct pcdir);
 546         }
 547         if (bp != NULL)
 548                 brelse(bp);
 549         return (1);
 550 }
 551 
 552 /*
 553  * Rename a file.
 554  */
 555 int
 556 pc_rename(
 557         struct pcnode *dp,              /* parent directory */
 558         struct pcnode *tdp,             /* target directory */
 559         char *snm,                      /* source file name */
 560         char *tnm,                      /* target file name */
 561         caller_context_t *ctp)
 562 {
 563         struct pcnode *pcp;     /* pcnode we are trying to rename */
 564         struct pcnode *tpcp = NULL; /* pcnode that's in our way */
 565         struct pcslot slot;
 566         int error;
 567         struct vnode *vp = PCTOV(dp);
 568         struct vnode *svp = NULL;
 569         struct pcfs *fsp = VFSTOPCFS(vp->v_vfsp);
 570         int filecasechange = 0;
 571         int oldisdir = 0;
 572 
 573         PC_DPRINTF3(4, "pc_rename(0x%p, %s, %s)\n", (void *)dp, snm, tnm);
 574         /*
 575          * Leading spaces are not allowed in DOS.
 576          */
 577         if (*tnm == ' ')
 578                 return (EINVAL);
 579         /*
 580          * No dot or dotdot.
 581          */
 582         if (PC_NAME_IS_DOT(snm) || PC_NAME_IS_DOTDOT(snm) ||
 583             PC_NAME_IS_DOT(tnm) || PC_NAME_IS_DOTDOT(tnm))
 584                 return (EINVAL);
 585         /*
 586          * Get the source node.  We'll jump back to here if trying to
 587          * move on top of an existing file, after deleting that file.
 588          */
 589 top:
 590         error = pc_findentry(dp, snm, &slot, NULL);
 591         if (error) {
 592                 return (error);
 593         }
 594         pcp = pc_getnode(VFSTOPCFS(vp->v_vfsp),
 595             slot.sl_blkno, slot.sl_offset, slot.sl_ep);
 596 
 597         brelse(slot.sl_bp);
 598 
 599         if (pcp)
 600                 svp = PCTOV(pcp);
 601 
 602         /*
 603          * is the rename invalid, i.e. rename("a", "a/a")
 604          */
 605         if (pcp == tdp) {
 606                 if (svp)
 607                         VN_RELE(svp);
 608                 return (EINVAL);
 609         }
 610 
 611         /*
 612          * Are we just changing the case of an existing name?
 613          */
 614         if ((dp->pc_scluster == tdp->pc_scluster) &&
 615             (u8_strcmp(snm, tnm, 0, U8_STRCMP_CI_UPPER, U8_UNICODE_LATEST,
 616             &error) == 0)) {
 617                 filecasechange = 1;
 618         }
 619 
 620         /*
 621          * u8_strcmp detected an illegal character
 622          */
 623         if (error)
 624                 return (EINVAL);
 625 
 626         oldisdir = pcp->pc_entry.pcd_attr & PCA_DIR;
 627 
 628         /*
 629          * see if the target exists
 630          */
 631         error = pc_findentry(tdp, tnm, &slot, NULL);
 632         if (error == 0 && filecasechange == 0) {
 633                 /*
 634                  * Target exists.  If it's a file, delete it.  If it's
 635                  * a directory, bail.
 636                  */
 637                 int newisdir;
 638 
 639                 tpcp = pc_getnode(VFSTOPCFS(vp->v_vfsp),
 640                     slot.sl_blkno, slot.sl_offset, slot.sl_ep);
 641 
 642                 newisdir = tpcp->pc_entry.pcd_attr & PCA_DIR;
 643 
 644                 brelse(slot.sl_bp);
 645 
 646                 /*
 647                  * Error cases (from rename(2)):
 648                  * old is dir, new is dir: EEXIST
 649                  * old is dir, new is nondir: ENOTDIR
 650                  * old is nondir, new is dir: EISDIR
 651                  */
 652                 if (!newisdir) {
 653                         if (oldisdir) {
 654                                 error = ENOTDIR;
 655                         } else {
 656                                 /* nondir/nondir, remove target */
 657                                 error = pc_dirremove(tdp, tnm,
 658                                     (struct vnode *)NULL, VREG, ctp);
 659                                 if (error == 0) {
 660                                         vnevent_rename_dest(PCTOV(tpcp),
 661                                             PCTOV(tdp), tnm, ctp);
 662                                         VN_RELE(PCTOV(tpcp));
 663                                         tpcp = NULL;
 664                                         VN_RELE(PCTOV(pcp));
 665                                         goto top;
 666                                 }
 667                         }
 668                 } else if (oldisdir) {
 669                         /* dir/dir, remove target */
 670                         error = pc_dirremove(tdp, tnm,
 671                             (struct vnode *)NULL, VDIR, ctp);
 672                         if (error == 0) {
 673                                 vnevent_rename_dest(PCTOV(tpcp), PCTOV(tdp),
 674                                     tnm, ctp);
 675                                 VN_RELE(PCTOV(tpcp));
 676                                 tpcp = NULL;
 677                                 VN_RELE(PCTOV(pcp));
 678                                 goto top;
 679                         }
 680                         /* Follow rename(2)'s spec... */
 681                         if (error == ENOTEMPTY) {
 682                                 error = EEXIST;
 683                         }
 684                 } else {
 685                         /* nondir/dir, bail */
 686                         error = EISDIR;
 687                 }
 688         }
 689 
 690         if ((error == 0) || (error == ENOENT)) {
 691                 offset_t lfn_offset = -1;
 692                 daddr_t blkno;
 693                 struct pcdir *direntries;
 694                 struct pcdir *ep;
 695                 int     ndirentries;
 696                 pc_cluster16_t pct_lo;
 697                 pc_cluster16_t pct_hi;
 698                 offset_t offset;
 699                 int     boff;
 700                 struct buf *bp = NULL;
 701                 uchar_t attr;
 702                 int     size;
 703                 struct pctime mtime;
 704                 struct pctime crtime;
 705                 uchar_t ntattr;
 706                 ushort_t ladate;
 707                 ushort_t eattr;
 708                 uchar_t crtime_msec;
 709 
 710                 /*
 711                  * Rename the source.
 712                  */
 713                 /*
 714                  * Delete the old name, and create a new name.
 715                  */
 716                 if (filecasechange == 1 && error == 0)
 717                         brelse(slot.sl_bp);
 718                 ndirentries = direntries_needed(tdp, tnm);
 719                 if (ndirentries == -1) {
 720                         error = EINVAL;
 721                         goto done;
 722                 }
 723                 /*
 724                  * first see if we have enough space to create the new
 725                  * name before destroying the old one.
 726                  */
 727                 offset = pc_find_free_space(tdp, ndirentries);
 728                 if (offset == -1) {
 729                         error = ENOSPC;
 730                         goto done;
 731                 }
 732 
 733                 error = pc_findentry(dp, snm, &slot, &lfn_offset);
 734                 if (error) {
 735                         goto done;
 736                 }
 737                 pct_lo = slot.sl_ep->pcd_scluster_lo;
 738                 if (IS_FAT32(fsp))
 739                         pct_hi = slot.sl_ep->un.pcd_scluster_hi;
 740                 else
 741                         eattr = slot.sl_ep->un.pcd_eattr;
 742                 size = slot.sl_ep->pcd_size;
 743                 attr = slot.sl_ep->pcd_attr;
 744                 mtime = slot.sl_ep->pcd_mtime;
 745                 crtime = slot.sl_ep->pcd_crtime;
 746                 crtime_msec = slot.sl_ep->pcd_crtime_msec;
 747                 ntattr = slot.sl_ep->pcd_ntattr;
 748                 ladate = slot.sl_ep->pcd_ladate;
 749 
 750                 if (lfn_offset != -1) {
 751                         brelse(slot.sl_bp);
 752                         error = pc_remove_long_fn(dp, lfn_offset);
 753                         if (error) {
 754                                 pc_mark_irrecov(VFSTOPCFS(vp->v_vfsp));
 755                                 goto done;
 756                         }
 757                 } else {
 758                         slot.sl_ep->pcd_filename[0] =
 759                             pcp->pc_entry.pcd_filename[0] = PCD_ERASED;
 760                         bwrite2(slot.sl_bp);
 761                         error = geterror(slot.sl_bp);
 762                         brelse(slot.sl_bp);
 763                 }
 764                 if (error) {
 765                         pc_mark_irrecov(VFSTOPCFS(vp->v_vfsp));
 766                         error = EIO;
 767                         goto done;
 768                 }
 769 
 770                 /*
 771                  * Make an entry from the supplied attributes.
 772                  */
 773                 direntries = pc_name_to_pcdir(tdp, tnm, ndirentries, &error);
 774                 if (direntries == NULL) {
 775                         goto done;
 776                 }
 777 
 778                 error = pc_makedirentry(tdp, direntries, ndirentries, NULL,
 779                     offset);
 780                 kmem_free(direntries, ndirentries * sizeof (struct pcdir));
 781                 if (error) {
 782                         goto done;
 783                 }
 784                 /* advance to short name */
 785                 offset += (ndirentries - 1)  * sizeof (struct pcdir);
 786                 boff = pc_blkoff(fsp, offset);
 787                 error = pc_blkatoff(tdp, offset, &bp, &ep);
 788                 if (error) {
 789                         goto done;
 790                 }
 791                 blkno = pc_daddrdb(fsp, bp->b_blkno);
 792                 ep->pcd_scluster_lo = pct_lo;
 793                 if (IS_FAT32(fsp))
 794                         ep->un.pcd_scluster_hi = pct_hi;
 795                 else
 796                         ep->un.pcd_eattr = eattr;
 797                 ep->pcd_size = size;
 798                 ep->pcd_attr = attr;
 799                 ep->pcd_mtime = mtime;
 800                 ep->pcd_crtime = crtime;
 801                 ep->pcd_crtime_msec = crtime_msec;
 802                 ep->pcd_ntattr = ntattr;
 803                 ep->pcd_ladate = ladate;
 804                 bwrite2(bp);
 805                 error = geterror(bp);
 806                 pcp->pc_eblkno = blkno;
 807                 pcp->pc_eoffset = boff;
 808                 pcp->pc_entry = *ep;
 809                 pcp->pc_flags |= PC_CHG;
 810                 brelse(bp);
 811                 if (error) {
 812                         pc_mark_irrecov(VFSTOPCFS(vp->v_vfsp));
 813                         error = EIO;
 814                         goto done;
 815                 }
 816                 /* No need to fix ".." if we're renaming within a dir */
 817                 if (oldisdir && dp != tdp) {
 818                         if ((error = pc_dirfixdotdot(pcp, dp, tdp)) != 0) {
 819                                 goto done;
 820                         }
 821                 }
 822                 if ((error = pc_nodeupdate(pcp)) != 0) {
 823                         goto done;
 824                 }
 825         }
 826 
 827         if (error == 0) {
 828                 vnevent_rename_src(PCTOV(pcp), PCTOV(dp), snm, ctp);
 829                 vnevent_rename_dest_dir(PCTOV(tdp), PCTOV(pcp), tnm, ctp);
 830         }
 831 
 832 done:
 833         if (tpcp != NULL)
 834                 VN_RELE(PCTOV(tpcp));
 835         VN_RELE(PCTOV(pcp));
 836 
 837         return (error);
 838 }
 839 
 840 /*
 841  * Fix the ".." entry of the child directory so that it points to the
 842  * new parent directory instead of the old one.
 843  */
 844 static int
 845 pc_dirfixdotdot(struct pcnode *dp,      /* child directory being moved */
 846         struct pcnode *opdp,            /* old parent directory */
 847         struct pcnode *npdp)            /* new parent directory */
 848 {
 849         pc_cluster32_t cn;
 850         struct vnode *vp = PCTOV(dp);
 851         struct pcfs *fsp = VFSTOPCFS(vp->v_vfsp);
 852         int error = 0;
 853         struct buf *bp = NULL;
 854         struct pcdir *ep = NULL;
 855         struct pcdir *tep = NULL;
 856 
 857         /*
 858          * set the new child's ".." directory entry starting cluster to
 859          * point to the new parent's starting cluster
 860          */
 861         ASSERT(opdp != npdp);
 862         error = pc_blkatoff(dp, (offset_t)0, &bp, &ep);
 863         if (error) {
 864                 PC_DPRINTF0(1, "pc_dirfixdotdot: error in blkatoff\n");
 865                 return (error);
 866         }
 867         tep = ep;
 868         ep++;
 869         if (!PC_SHORTNAME_IS_DOT(tep->pcd_filename) &&
 870             !PC_SHORTNAME_IS_DOTDOT(ep->pcd_filename)) {
 871                 PC_DPRINTF0(1, "pc_dirfixdotdot: mangled directory entry\n");
 872                 error = ENOTDIR;
 873                 return (error);
 874         }
 875         cn = pc_getstartcluster(fsp, &npdp->pc_entry);
 876         pc_setstartcluster(fsp, ep, cn);
 877 
 878         bwrite2(bp);
 879         error = geterror(bp);
 880         brelse(bp);
 881         if (error) {
 882                 PC_DPRINTF0(1, "pc_dirfixdotdot: error in write\n");
 883                 pc_mark_irrecov(fsp);
 884                 return (EIO);
 885         }
 886         return (0);
 887 }
 888 
 889 
 890 /*
 891  * Search a directory for an entry.
 892  * The directory should be locked as this routine
 893  * will sleep on I/O while searching.
 894  */
 895 static int
 896 pc_findentry(
 897         struct pcnode *dp,              /* parent directory */
 898         char *namep,                    /* name to lookup */
 899         struct pcslot *slotp,
 900         offset_t *lfn_offset)
 901 {
 902         offset_t offset;
 903         struct pcdir *ep = NULL;
 904         int boff;
 905         int error;
 906         struct vnode *vp;
 907         struct pcfs *fsp;
 908 
 909         vp = PCTOV(dp);
 910         PC_DPRINTF2(6, "pc_findentry: looking for %s in dir 0x%p\n", namep,
 911             (void *)dp);
 912         slotp->sl_status = SL_NONE;
 913         if (!(dp->pc_entry.pcd_attr & PCA_DIR)) {
 914                 return (ENOTDIR);
 915         }
 916         /*
 917          * Verify that the dp is still valid on the disk
 918          */
 919         fsp = VFSTOPCFS(vp->v_vfsp);
 920         error = pc_verify(fsp);
 921         if (error)
 922                 return (error);
 923 
 924         slotp->sl_bp = NULL;
 925         offset = 0;
 926         for (;;) {
 927                 /*
 928                  * If offset is on a block boundary,
 929                  * read in the next directory block.
 930                  * Release previous if it exists.
 931                  */
 932                 boff = pc_blkoff(fsp, offset);
 933                 if (boff == 0 || slotp->sl_bp == NULL ||
 934                     boff >= slotp->sl_bp->b_bcount) {
 935                         if (slotp->sl_bp != NULL) {
 936                                 brelse(slotp->sl_bp);
 937                                 slotp->sl_bp = NULL;
 938                         }
 939                         error = pc_blkatoff(dp, offset, &slotp->sl_bp, &ep);
 940                         if (error == ENOENT && slotp->sl_status == SL_NONE) {
 941                                 slotp->sl_status = SL_EXTEND;
 942                                 slotp->sl_offset = (int)offset;
 943                         }
 944                         if (error)
 945                                 return (error);
 946                 }
 947                 if ((ep->pcd_filename[0] == PCD_UNUSED) ||
 948                     (ep->pcd_filename[0] == PCD_ERASED)) {
 949                         /*
 950                          * note empty slots, in case name is not found
 951                          */
 952                         if (slotp->sl_status == SL_NONE) {
 953                                 slotp->sl_status = SL_FOUND;
 954                                 slotp->sl_blkno = pc_daddrdb(fsp,
 955                                     slotp->sl_bp->b_blkno);
 956                                 slotp->sl_offset = boff;
 957                         }
 958                         /*
 959                          * If unused we've hit the end of the directory
 960                          */
 961                         if (ep->pcd_filename[0] == PCD_UNUSED)
 962                                 break;
 963                         offset += sizeof (struct pcdir);
 964                         ep++;
 965                         continue;
 966                 }
 967                 if (PCDL_IS_LFN(ep)) {
 968                         offset_t t = offset;
 969                         if (pc_match_long_fn(dp, namep, &ep,
 970                             slotp, &offset) == 0) {
 971                                 if (lfn_offset != NULL)
 972                                         *lfn_offset = t;
 973                                 return (0);
 974                         }
 975                         continue;
 976                 }
 977                 if (pc_match_short_fn(dp, namep, &ep, slotp, &offset) == 0)
 978                         return (0);
 979         }
 980         if (slotp->sl_bp != NULL) {
 981                 brelse(slotp->sl_bp);
 982                 slotp->sl_bp = NULL;
 983         }
 984         return (ENOENT);
 985 }
 986 
 987 /*
 988  * Obtain the block at offset "offset" in file pcp.
 989  */
 990 int
 991 pc_blkatoff(
 992         struct pcnode *pcp,
 993         offset_t offset,
 994         struct buf **bpp,
 995         struct pcdir **epp)
 996 {
 997         struct pcfs *fsp;
 998         struct buf *bp;
 999         int size;
1000         int error;
1001         daddr_t bn;
1002 
1003         fsp = VFSTOPCFS(PCTOV(pcp)->v_vfsp);
1004         size = pc_blksize(fsp, pcp, offset);
1005         if (pc_blkoff(fsp, offset) >= size) {
1006                 PC_DPRINTF0(5, "pc_blkatoff: ENOENT\n");
1007                 return (ENOENT);
1008         }
1009         error = pc_bmap(pcp, pc_lblkno(fsp, offset), &bn, (uint_t *)0);
1010         if (error)
1011                 return (error);
1012 
1013         bp = bread(fsp->pcfs_xdev, bn, size);
1014         if (bp->b_flags & B_ERROR) {
1015                 PC_DPRINTF0(1, "pc_blkatoff: error\n");
1016                 brelse(bp);
1017                 pc_mark_irrecov(fsp);
1018                 return (EIO);
1019         }
1020         if (epp) {
1021                 *epp =
1022                     (struct pcdir *)(bp->b_un.b_addr + pc_blkoff(fsp, offset));
1023         }
1024         *bpp = bp;
1025         return (0);
1026 }
1027 
1028 /*
1029  * Parse user filename into the pc form of "filename.extension".
1030  * If names are too long for the format (and enable_long_filenames is set)
1031  * it returns EINVAL (since either this name was read from the disk (so
1032  * it must fit), _or_ we're trying to match a long file name (so we
1033  * should fail).  Tests for characters that are invalid in PCDOS and
1034  * converts to upper case (unless foldcase is 0).
1035  */
1036 static int
1037 pc_parsename(
1038         char *namep,
1039         char *fnamep,
1040         char *fextp)
1041 {
1042         int n;
1043         char c;
1044 
1045         n = PCFNAMESIZE;
1046         c = *namep++;
1047         if (c == 0)
1048                 return (EINVAL);
1049         if (c == '.') {
1050                 /*
1051                  * check for "." and "..".
1052                  */
1053                 *fnamep++ = c;
1054                 n--;
1055                 if (c = *namep++) {
1056                         if ((c != '.') || (c = *namep)) /* ".x" or "..x" */
1057                                 return (EINVAL);
1058                         *fnamep++ = '.';
1059                         n--;
1060                 }
1061         } else {
1062                 /*
1063                  * filename up to '.'
1064                  */
1065                 do {
1066                         if (n-- > 0) {
1067                                 c = toupper(c);
1068                                 if (!pc_validchar(c))
1069                                         return (EINVAL);
1070                                 *fnamep++ = c;
1071                         } else {
1072                                 /* not short */
1073                                 if (enable_long_filenames)
1074                                         return (EINVAL);
1075                         }
1076                 } while ((c = *namep++) != '\0' && c != '.');
1077         }
1078         while (n-- > 0) {            /* fill with blanks */
1079                 *fnamep++ = ' ';
1080         }
1081         /*
1082          * remainder is extension
1083          */
1084         n = PCFEXTSIZE;
1085         if (c == '.') {
1086                 while ((c = *namep++) != '\0' && n--) {
1087                         c = toupper(c);
1088                         if (!pc_validchar(c))
1089                                 return (EINVAL);
1090                         *fextp++ = c;
1091                 }
1092                 if (enable_long_filenames && (c != '\0')) {
1093                         /* not short */
1094                         return (EINVAL);
1095                 }
1096         }
1097         while (n-- > 0) {            /* fill with blanks */
1098                 *fextp++ = ' ';
1099         }
1100         return (0);
1101 }
1102 
1103 /*
1104  * Match a long filename entry with 'namep'. Also return failure
1105  * if the long filename isn't valid.
1106  */
1107 int
1108 pc_match_long_fn(struct pcnode *pcp, char *namep, struct pcdir **epp,
1109     struct pcslot *slotp, offset_t *offset)
1110 {
1111         struct pcdir *ep = (struct pcdir *)*epp;
1112         struct vnode *vp = PCTOV(pcp);
1113         struct pcfs *fsp = VFSTOPCFS(vp->v_vfsp);
1114         int     error = 0;
1115         char    lfn[PCMAXNAMLEN+1];
1116 
1117         error = pc_extract_long_fn(pcp, lfn, epp, offset, &slotp->sl_bp);
1118         if (error) {
1119                 if (error == EINVAL) {
1120                         return (ENOENT);
1121                 } else
1122                         return (error);
1123         }
1124         ep = *epp;
1125         if ((u8_strcmp(lfn, namep, 0, U8_STRCMP_CI_UPPER,
1126             U8_UNICODE_LATEST, &error) == 0) && (error == 0)) {
1127                 /* match */
1128                 slotp->sl_flags = 0;
1129                 slotp->sl_blkno = pc_daddrdb(fsp, slotp->sl_bp->b_blkno);
1130                 slotp->sl_offset = pc_blkoff(fsp, *offset);
1131                 slotp->sl_ep = ep;
1132                 return (0);
1133         }
1134         *offset += sizeof (struct pcdir);
1135         ep++;
1136         *epp = ep;
1137         /* If u8_strcmp detected an error it's sufficient to rtn ENOENT */
1138         return (ENOENT);
1139 }
1140 
1141 /*
1142  * Match a short filename entry with namep.
1143  */
1144 int
1145 pc_match_short_fn(struct pcnode *pcp, char *namep, struct pcdir **epp,
1146     struct pcslot *slotp, offset_t *offset)
1147 {
1148         char fname[PCFNAMESIZE];
1149         char fext[PCFEXTSIZE];
1150         struct pcdir *ep = *epp;
1151         int     error;
1152         struct vnode *vp = PCTOV(pcp);
1153         struct pcfs *fsp = VFSTOPCFS(vp->v_vfsp);
1154         int boff = pc_blkoff(fsp, *offset);
1155 
1156         if (PCA_IS_HIDDEN(fsp, ep->pcd_attr)) {
1157                 *offset += sizeof (struct pcdir);
1158                 ep++;
1159                 *epp = ep;
1160                 return (ENOENT);
1161         }
1162 
1163         error = pc_parsename(namep, fname, fext);
1164         if (error) {
1165                 *offset += sizeof (struct pcdir);
1166                 ep++;
1167                 *epp = ep;
1168                 return (error);
1169         }
1170 
1171         if ((bcmp(fname, ep->pcd_filename, PCFNAMESIZE) == 0) &&
1172             (bcmp(fext, ep->pcd_ext, PCFEXTSIZE) == 0)) {
1173                 /*
1174                  * found the file
1175                  */
1176                 if (fname[0] == '.') {
1177                         if (fname[1] == '.')
1178                                 slotp->sl_flags = SL_DOTDOT;
1179                         else
1180                                 slotp->sl_flags = SL_DOT;
1181                 } else {
1182                         slotp->sl_flags = 0;
1183                 }
1184                 slotp->sl_blkno =
1185                     pc_daddrdb(fsp, slotp->sl_bp->b_blkno);
1186                 slotp->sl_offset = boff;
1187                 slotp->sl_ep = ep;
1188                 return (0);
1189         }
1190         *offset += sizeof (struct pcdir);
1191         ep++;
1192         *epp = ep;
1193         return (ENOENT);
1194 }
1195 
1196 /*
1197  * Remove a long filename entry starting at lfn_offset. It must be
1198  * a valid entry or we wouldn't have gotten here. Also remove the
1199  * short filename entry.
1200  */
1201 static int
1202 pc_remove_long_fn(struct pcnode *pcp, offset_t lfn_offset)
1203 {
1204         struct vnode *vp = PCTOV(pcp);
1205         struct pcfs *fsp = VFSTOPCFS(vp->v_vfsp);
1206         int boff;
1207         struct buf *bp = NULL;
1208         struct pcdir *ep = NULL;
1209         int     error = 0;
1210 
1211         /*
1212          * if we're in here, we know that the lfn is in the proper format
1213          * of <series-of-lfn-entries> followed by <sfn-entry>
1214          */
1215         for (;;) {
1216                 boff = pc_blkoff(fsp, lfn_offset);
1217                 if (boff == 0 || bp == NULL || boff >= bp->b_bcount) {
1218                         if (bp != NULL) {
1219                                 bwrite2(bp);
1220                                 error = geterror(bp);
1221                                 brelse(bp);
1222                                 if (error)
1223                                         return (error);
1224                                 bp = NULL;
1225                         }
1226                         error = pc_blkatoff(pcp, lfn_offset, &bp, &ep);
1227                         if (error)
1228                                 return (error);
1229                 }
1230                 if (!PCDL_IS_LFN(ep)) {
1231                         /* done */
1232                         break;
1233                 }
1234                 /* zap it */
1235                 ep->pcd_filename[0] = PCD_ERASED;
1236                 ep->pcd_attr = 0;
1237                 lfn_offset += sizeof (struct pcdir);
1238                 ep++;
1239         }
1240         /* now we're on the short entry */
1241 
1242         ep->pcd_filename[0] = PCD_ERASED;
1243         ep->pcd_attr = 0;
1244 
1245         if (bp != NULL) {
1246                 bwrite2(bp);
1247                 error = geterror(bp);
1248                 brelse(bp);
1249                 if (error)
1250                         return (error);
1251         }
1252         return (0);
1253 }
1254 
1255 /*
1256  * Find (and allocate) space in the directory denoted by
1257  * 'pcp'. for 'ndirentries' pcdir structures.
1258  * Return the offset at which to start, or -1 for failure.
1259  */
1260 static offset_t
1261 pc_find_free_space(struct pcnode *pcp, int ndirentries)
1262 {
1263         offset_t offset = 0;
1264         offset_t spaceneeded = ndirentries * sizeof (struct pcdir);
1265         offset_t spaceoffset;
1266         offset_t spaceavail = 0;
1267         int boff;
1268         struct buf *bp = NULL;
1269         struct vnode *vp = PCTOV(pcp);
1270         struct pcfs *fsp = VFSTOPCFS(vp->v_vfsp);
1271         struct pcdir *ep;
1272         int     error;
1273 
1274         spaceoffset = offset;
1275         while (spaceneeded > spaceavail) {
1276                 /*
1277                  * If offset is on a block boundary,
1278                  * read in the next directory block.
1279                  * Release previous if it exists.
1280                  */
1281                 boff = pc_blkoff(fsp, offset);
1282                 if (boff == 0 || bp == NULL || boff >= bp->b_bcount) {
1283                         if (bp != NULL) {
1284                                 brelse(bp);
1285                                 bp = NULL;
1286                         }
1287                         error = pc_blkatoff(pcp, offset, &bp, &ep);
1288                         if (error == ENOENT) {
1289                                 daddr_t bn;
1290 
1291                                 /* extend directory */
1292                                 if (!IS_FAT32(fsp) && (vp->v_flag & VROOT))
1293                                         return (-1);
1294                                 while (spaceneeded > spaceavail) {
1295                                         error = pc_balloc(pcp,
1296                                             pc_lblkno(fsp, offset), 1, &bn);
1297                                         if (error)
1298                                                 return (-1);
1299                                         pcp->pc_size += fsp->pcfs_clsize;
1300                                         spaceavail += fsp->pcfs_clsize;
1301                                         offset += fsp->pcfs_clsize;
1302                                 }
1303                                 return (spaceoffset);
1304                         }
1305                         if (error)
1306                                 return (-1);
1307                 }
1308                 if ((ep->pcd_filename[0] == PCD_UNUSED) ||
1309                     (ep->pcd_filename[0] == PCD_ERASED)) {
1310                         offset += sizeof (struct pcdir);
1311                         spaceavail += sizeof (struct pcdir);
1312                         ep++;
1313                         continue;
1314                 }
1315                 offset += sizeof (struct pcdir);
1316                 spaceavail = 0;
1317                 spaceoffset = offset;
1318                 ep++;
1319         }
1320         if (bp != NULL) {
1321                 brelse(bp);
1322         }
1323         return (spaceoffset);
1324 }
1325 
1326 /*
1327  * Return how many long filename entries are needed.
1328  * A maximum of PCLFNCHUNKSIZE characters per entry, plus one for a
1329  * short filename.
1330  */
1331 static int
1332 direntries_needed(struct pcnode *dp, char *namep)
1333 {
1334         struct pcdir ep;
1335         uint16_t *w2_str;
1336         size_t  u8l, u16l;
1337         int ret;
1338 
1339         if (enable_long_filenames == 0) {
1340                 return (1);
1341         }
1342         if (pc_is_short_file_name(namep, 0)) {
1343                 (void) pc_parsename(namep, ep.pcd_filename, ep.pcd_ext);
1344                 if (!shortname_exists(dp, ep.pcd_filename, ep.pcd_ext)) {
1345                         return (1);
1346                 }
1347         }
1348         if (pc_valid_long_fn(namep, 1)) {
1349                 /*
1350                  * convert to UTF-16 or UNICODE for calculating the entries
1351                  * needed. Conversion will consume at the most 512 bytes
1352                  */
1353                 u16l = PCMAXNAMLEN + 1;
1354                 w2_str = (uint16_t *)kmem_zalloc(PCMAXNAM_UTF16, KM_SLEEP);
1355                 u8l = strlen(namep);
1356                 ret = uconv_u8tou16((const uchar_t *)namep, &u8l,
1357                     w2_str, &u16l, UCONV_OUT_LITTLE_ENDIAN);
1358                 kmem_free((caddr_t)w2_str, PCMAXNAM_UTF16);
1359                 if (ret == 0) {
1360                         ret = 1 + u16l / PCLFNCHUNKSIZE;
1361                         if (u16l % PCLFNCHUNKSIZE != 0)
1362                                 ret++;
1363                         return (ret);
1364                 }
1365         }
1366         return (-1);
1367 }
1368 
1369 /*
1370  * Allocate and return an array of pcdir structures for the passed-in
1371  * name. ndirentries tells how many are required (including the short
1372  * filename entry). Just allocate and fill them in properly here so they
1373  * can be written out.
1374  */
1375 static struct pcdir *
1376 pc_name_to_pcdir(struct pcnode *dp, char *namep, int ndirentries, int *errret)
1377 {
1378         struct pcdir *bpcdir;
1379         struct pcdir *ep;
1380         struct pcdir_lfn *lep;
1381         int     i;
1382         uchar_t cksum;
1383         int     nchars;
1384         int     error = 0;
1385         char    *nameend;
1386         uint16_t *w2_str;
1387         size_t  u8l, u16l;
1388         int ret;
1389 
1390         bpcdir = kmem_zalloc(ndirentries * sizeof (struct pcdir), KM_SLEEP);
1391         ep = &bpcdir[ndirentries - 1];
1392         if (ndirentries == 1) {
1393                 (void) pc_parsename(namep, ep->pcd_filename, ep->pcd_ext);
1394                 return (bpcdir);
1395         }
1396 
1397         /* Here we need to convert to UTF-16 or UNICODE for writing */
1398 
1399         u16l = PCMAXNAMLEN + 1;
1400         w2_str = (uint16_t *)kmem_zalloc(PCMAXNAM_UTF16, KM_SLEEP);
1401         u8l = strlen(namep);
1402         ret = uconv_u8tou16((const uchar_t *)namep, &u8l, w2_str, &u16l,
1403             UCONV_OUT_LITTLE_ENDIAN);
1404         if (ret != 0) {
1405                 kmem_free((caddr_t)w2_str, PCMAXNAM_UTF16);
1406                 *errret = ret;
1407                 return (NULL);
1408         }
1409         nameend = (char *)(w2_str + u16l);
1410         u16l %= PCLFNCHUNKSIZE;
1411         if (u16l != 0) {
1412                 nchars = u16l + 1;
1413                 nameend += 2;
1414         } else {
1415                 nchars = PCLFNCHUNKSIZE;
1416         }
1417         nchars *= sizeof (uint16_t);
1418 
1419         /* short file name */
1420         error = generate_short_name(dp, namep, ep);
1421         if (error) {
1422                 kmem_free(bpcdir, ndirentries * sizeof (struct pcdir));
1423                 *errret = error;
1424                 return (NULL);
1425         }
1426         cksum = pc_checksum_long_fn(ep->pcd_filename, ep->pcd_ext);
1427         for (i = 0; i < (ndirentries - 1); i++) {
1428                 /* long file name */
1429                 nameend -= nchars;
1430                 lep = (struct pcdir_lfn *)&bpcdir[i];
1431                 set_long_fn_chunk(lep, nameend, nchars);
1432                 lep->pcdl_attr = PCDL_LFN_BITS;
1433                 lep->pcdl_checksum = cksum;
1434                 lep->pcdl_ordinal = (uchar_t)(ndirentries - i - 1);
1435                 nchars = PCLFNCHUNKSIZE * sizeof (uint16_t);
1436         }
1437         kmem_free((caddr_t)w2_str, PCMAXNAM_UTF16);
1438         lep = (struct pcdir_lfn *)&bpcdir[0];
1439         lep->pcdl_ordinal |= 0x40;
1440         return (bpcdir);
1441 }
1442 
1443 static int
1444 generate_short_name(struct pcnode *dp, char *namep, struct pcdir *inep)
1445 {
1446         int     rev;
1447         int     nchars;
1448         int     i, j;
1449         char    *dot = NULL;
1450         char    fname[PCFNAMESIZE+1];
1451         char    fext[PCFEXTSIZE+1];
1452         char    scratch[8];
1453         int     error = 0;
1454         struct  pcslot slot;
1455         char    shortname[20];
1456         int     force_tilde = 0;
1457 
1458         /*
1459          * generate a unique short file name based on the long input name.
1460          *
1461          * Say, for "This is a very long filename.txt" generate
1462          * "THISIS~1.TXT", or "THISIS~2.TXT" if that's already there.
1463          * Skip invalid short name characters in the long name, plus
1464          * a couple NT skips (space and reverse backslash).
1465          *
1466          * Unfortunately, since this name would be hidden by the normal
1467          * lookup routine, we need to look for it ourselves. But luckily
1468          * we don't need to look at the lfn entries themselves.
1469          */
1470         force_tilde = !pc_is_short_file_name(namep, 1);
1471 
1472         /*
1473          * Strip off leading invalid characters.
1474          * We need this because names like '.login' are now ok, but the
1475          * short name needs to be something like LOGIN~1.
1476          */
1477         for (; *namep != '\0'; namep++) {
1478                 if (*namep == ' ')
1479                         continue;
1480                 if (!pc_validchar(*namep) && !pc_validchar(toupper(*namep)))
1481                         continue;
1482                 break;
1483         }
1484         dot = strrchr(namep, '.');
1485         if (dot != NULL) {
1486                 dot++;
1487                 for (j = 0, i = 0; j < PCFEXTSIZE; i++) {
1488                         if (dot[i] == '\0')
1489                                 break;
1490                         /* skip valid, but not generally good characters */
1491                         if (dot[i] == ' ' || dot[i] == '\\')
1492                                 continue;
1493                         if (pc_validchar(dot[i]))
1494                                 fext[j++] = dot[i];
1495                         else if (pc_validchar(toupper(dot[i])))
1496                                 fext[j++] = toupper(dot[i]);
1497                 }
1498                 for (i = j; i < PCFEXTSIZE; i++)
1499                         fext[i] = ' ';
1500                 dot--;
1501         } else {
1502                 for (i = 0; i < PCFEXTSIZE; i++) {
1503                         fext[i] = ' ';
1504                 }
1505         }
1506         /*
1507          * We know we're a long name, not a short name (or we wouldn't
1508          * be here at all. But if uppercasing ourselves would be a short
1509          * name, then we can possibly avoid the ~N format.
1510          */
1511         if (!force_tilde)
1512                 rev = 0;
1513         else
1514                 rev = 1;
1515         for (;;) {
1516                 bzero(fname, sizeof (fname));
1517                 nchars = PCFNAMESIZE;
1518                 if (rev) {
1519                         nchars--; /* ~ */
1520                         i = rev;
1521                         do {
1522                                 nchars--;
1523                                 i /= 10;
1524                         } while (i);
1525                         if (nchars <= 0) {
1526                                 return (ENOSPC);
1527                         }
1528                 }
1529                 for (j = 0, i = 0; j < nchars; i++) {
1530                         if ((&namep[i] == dot) || (namep[i] == '\0'))
1531                                 break;
1532                         /* skip valid, but not generally good characters */
1533                         if (namep[i] == ' ' || namep[i] == '\\')
1534                                 continue;
1535                         if (pc_validchar(namep[i]))
1536                                 fname[j++] = namep[i];
1537                         else if (pc_validchar(toupper(namep[i])))
1538                                 fname[j++] = toupper(namep[i]);
1539                 }
1540                 if (rev) {
1541                         (void) sprintf(scratch, "~%d", rev);
1542                         (void) strcat(fname, scratch);
1543                 }
1544                 for (i = strlen(fname); i < PCFNAMESIZE; i++)
1545                         fname[i] = ' ';
1546                 /* now see if it exists */
1547                 (void) pc_fname_ext_to_name(shortname, fname, fext, 0);
1548                 error = pc_findentry(dp, shortname, &slot, NULL);
1549                 if (error == 0) {
1550                         /* found it */
1551                         brelse(slot.sl_bp);
1552                         rev++;
1553                         continue;
1554                 }
1555                 if (!shortname_exists(dp, fname, fext))
1556                         break;
1557                 rev++;
1558         }
1559         (void) strncpy(inep->pcd_filename, fname, PCFNAMESIZE);
1560         (void) strncpy(inep->pcd_ext, fext, PCFEXTSIZE);
1561         return (0);
1562 }
1563 
1564 /*
1565  * Returns 1 if the passed-in filename is a short name, 0 if not.
1566  */
1567 static int
1568 pc_is_short_file_name(char *namep, int foldcase)
1569 {
1570         int     i;
1571         char    c;
1572 
1573         for (i = 0; i < PCFNAMESIZE; i++, namep++) {
1574                 if (*namep == '\0')
1575                         return (1);
1576                 if (*namep == '.')
1577                         break;
1578                 if (foldcase)
1579                         c = toupper(*namep);
1580                 else
1581                         c = *namep;
1582                 if (!pc_validchar(c))
1583                         return (0);
1584         }
1585         if (*namep == '\0')
1586                 return (1);
1587         if (*namep != '.')
1588                 return (0);
1589         namep++;
1590         for (i = 0; i < PCFEXTSIZE; i++, namep++) {
1591                 if (*namep == '\0')
1592                         return (1);
1593                 if (foldcase)
1594                         c = toupper(*namep);
1595                 else
1596                         c = *namep;
1597                 if (!pc_validchar(c))
1598                         return (0);
1599         }
1600         /* we should be done. If not... */
1601         if (*namep == '\0')
1602                 return (1);
1603         return (0);
1604 
1605 }
1606 
1607 /*
1608  * We call this when we want to see if a short filename already exists
1609  * in the filesystem as part of a long filename. When creating a short
1610  * name (FILENAME.TXT from the user, or when generating one for a long
1611  * filename), we cannot allow one that is part of a long filename.
1612  * pc_findentry will find all the names that are visible (long or short),
1613  * but will not crack any long filename entries.
1614  */
1615 static int
1616 shortname_exists(struct pcnode *dp, char *fname, char *fext)
1617 {
1618         struct buf *bp = NULL;
1619         int     offset = 0;
1620         int     match = 0;
1621         struct pcdir *ep;
1622         struct vnode *vp = PCTOV(dp);
1623         struct pcfs *fsp = VFSTOPCFS(vp->v_vfsp);
1624         int     boff;
1625         int     error = 0;
1626 
1627         for (;;) {
1628                 boff = pc_blkoff(fsp, offset);
1629                 if (boff == 0 || bp == NULL || boff >= bp->b_bcount) {
1630                         if (bp != NULL) {
1631                                 brelse(bp);
1632                                 bp = NULL;
1633                         }
1634                         error = pc_blkatoff(dp, offset, &bp, &ep);
1635                         if (error == ENOENT)
1636                                 break;
1637                         if (error) {
1638                                 return (1);
1639                         }
1640                 }
1641                 if (PCDL_IS_LFN(ep) ||
1642                     (ep->pcd_filename[0] == PCD_ERASED)) {
1643                         offset += sizeof (struct pcdir);
1644                         ep++;
1645                         continue;
1646                 }
1647                 if (ep->pcd_filename[0] == PCD_UNUSED)
1648                         break;
1649                 /*
1650                  * in use, and a short file name (either standalone
1651                  * or associated with a long name
1652                  */
1653                 if ((bcmp(fname, ep->pcd_filename, PCFNAMESIZE) == 0) &&
1654                     (bcmp(fext, ep->pcd_ext, PCFEXTSIZE) == 0)) {
1655                         match = 1;
1656                         break;
1657                 }
1658                 offset += sizeof (struct pcdir);
1659                 ep++;
1660         }
1661         if (bp) {
1662                 brelse(bp);
1663                 bp = NULL;
1664         }
1665         return (match);
1666 }
1667 
1668 pc_cluster32_t
1669 pc_getstartcluster(struct pcfs *fsp, struct pcdir *ep)
1670 {
1671         if (IS_FAT32(fsp)) {
1672                 pc_cluster32_t cn;
1673                 pc_cluster16_t hi16;
1674                 pc_cluster16_t lo16;
1675 
1676                 hi16 = ltohs(ep->un.pcd_scluster_hi);
1677                 lo16 = ltohs(ep->pcd_scluster_lo);
1678                 cn = (hi16 << 16) | lo16;
1679                 return (cn);
1680         } else {
1681                 return (ltohs(ep->pcd_scluster_lo));
1682         }
1683 }
1684 
1685 void
1686 pc_setstartcluster(struct pcfs *fsp, struct pcdir *ep, pc_cluster32_t cln)
1687 {
1688         if (IS_FAT32(fsp)) {
1689                 pc_cluster16_t hi16;
1690                 pc_cluster16_t lo16;
1691 
1692                 hi16 = (cln >> 16) & 0xFFFF;
1693                 lo16 = cln & 0xFFFF;
1694                 ep->un.pcd_scluster_hi = htols(hi16);
1695                 ep->pcd_scluster_lo = htols(lo16);
1696         } else {
1697                 pc_cluster16_t cln16;
1698 
1699                 cln16 = (pc_cluster16_t)cln;
1700                 ep->pcd_scluster_lo = htols(cln16);
1701         }
1702 }