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 2015 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                 if (dp != tdp)
 830                         vnevent_rename_dest_dir(PCTOV(tdp), ctp);
 831         }
 832 
 833 done:
 834         if (tpcp != NULL)
 835                 VN_RELE(PCTOV(tpcp));
 836         VN_RELE(PCTOV(pcp));
 837 
 838         return (error);
 839 }
 840 
 841 /*
 842  * Fix the ".." entry of the child directory so that it points to the
 843  * new parent directory instead of the old one.
 844  */
 845 static int
 846 pc_dirfixdotdot(struct pcnode *dp,      /* child directory being moved */
 847         struct pcnode *opdp,            /* old parent directory */
 848         struct pcnode *npdp)            /* new parent directory */
 849 {
 850         pc_cluster32_t cn;
 851         struct vnode *vp = PCTOV(dp);
 852         struct pcfs *fsp = VFSTOPCFS(vp->v_vfsp);
 853         int error = 0;
 854         struct buf *bp = NULL;
 855         struct pcdir *ep = NULL;
 856         struct pcdir *tep = NULL;
 857 
 858         /*
 859          * set the new child's ".." directory entry starting cluster to
 860          * point to the new parent's starting cluster
 861          */
 862         ASSERT(opdp != npdp);
 863         error = pc_blkatoff(dp, (offset_t)0, &bp, &ep);
 864         if (error) {
 865                 PC_DPRINTF0(1, "pc_dirfixdotdot: error in blkatoff\n");
 866                 return (error);
 867         }
 868         tep = ep;
 869         ep++;
 870         if (!PC_SHORTNAME_IS_DOT(tep->pcd_filename) &&
 871             !PC_SHORTNAME_IS_DOTDOT(ep->pcd_filename)) {
 872                 PC_DPRINTF0(1, "pc_dirfixdotdot: mangled directory entry\n");
 873                 error = ENOTDIR;
 874                 return (error);
 875         }
 876         cn = pc_getstartcluster(fsp, &npdp->pc_entry);
 877         pc_setstartcluster(fsp, ep, cn);
 878 
 879         bwrite2(bp);
 880         error = geterror(bp);
 881         brelse(bp);
 882         if (error) {
 883                 PC_DPRINTF0(1, "pc_dirfixdotdot: error in write\n");
 884                 pc_mark_irrecov(fsp);
 885                 return (EIO);
 886         }
 887         return (0);
 888 }
 889 
 890 
 891 /*
 892  * Search a directory for an entry.
 893  * The directory should be locked as this routine
 894  * will sleep on I/O while searching.
 895  */
 896 static int
 897 pc_findentry(
 898         struct pcnode *dp,              /* parent directory */
 899         char *namep,                    /* name to lookup */
 900         struct pcslot *slotp,
 901         offset_t *lfn_offset)
 902 {
 903         offset_t offset;
 904         struct pcdir *ep = NULL;
 905         int boff;
 906         int error;
 907         struct vnode *vp;
 908         struct pcfs *fsp;
 909 
 910         vp = PCTOV(dp);
 911         PC_DPRINTF2(6, "pc_findentry: looking for %s in dir 0x%p\n", namep,
 912             (void *)dp);
 913         slotp->sl_status = SL_NONE;
 914         if (!(dp->pc_entry.pcd_attr & PCA_DIR)) {
 915                 return (ENOTDIR);
 916         }
 917         /*
 918          * Verify that the dp is still valid on the disk
 919          */
 920         fsp = VFSTOPCFS(vp->v_vfsp);
 921         error = pc_verify(fsp);
 922         if (error)
 923                 return (error);
 924 
 925         slotp->sl_bp = NULL;
 926         offset = 0;
 927         for (;;) {
 928                 /*
 929                  * If offset is on a block boundary,
 930                  * read in the next directory block.
 931                  * Release previous if it exists.
 932                  */
 933                 boff = pc_blkoff(fsp, offset);
 934                 if (boff == 0 || slotp->sl_bp == NULL ||
 935                     boff >= slotp->sl_bp->b_bcount) {
 936                         if (slotp->sl_bp != NULL) {
 937                                 brelse(slotp->sl_bp);
 938                                 slotp->sl_bp = NULL;
 939                         }
 940                         error = pc_blkatoff(dp, offset, &slotp->sl_bp, &ep);
 941                         if (error == ENOENT && slotp->sl_status == SL_NONE) {
 942                                 slotp->sl_status = SL_EXTEND;
 943                                 slotp->sl_offset = (int)offset;
 944                         }
 945                         if (error)
 946                                 return (error);
 947                 }
 948                 if ((ep->pcd_filename[0] == PCD_UNUSED) ||
 949                     (ep->pcd_filename[0] == PCD_ERASED)) {
 950                         /*
 951                          * note empty slots, in case name is not found
 952                          */
 953                         if (slotp->sl_status == SL_NONE) {
 954                                 slotp->sl_status = SL_FOUND;
 955                                 slotp->sl_blkno = pc_daddrdb(fsp,
 956                                     slotp->sl_bp->b_blkno);
 957                                 slotp->sl_offset = boff;
 958                         }
 959                         /*
 960                          * If unused we've hit the end of the directory
 961                          */
 962                         if (ep->pcd_filename[0] == PCD_UNUSED)
 963                                 break;
 964                         offset += sizeof (struct pcdir);
 965                         ep++;
 966                         continue;
 967                 }
 968                 if (PCDL_IS_LFN(ep)) {
 969                         offset_t t = offset;
 970                         if (pc_match_long_fn(dp, namep, &ep,
 971                             slotp, &offset) == 0) {
 972                                 if (lfn_offset != NULL)
 973                                         *lfn_offset = t;
 974                                 return (0);
 975                         }
 976                         continue;
 977                 }
 978                 if (pc_match_short_fn(dp, namep, &ep, slotp, &offset) == 0)
 979                         return (0);
 980         }
 981         if (slotp->sl_bp != NULL) {
 982                 brelse(slotp->sl_bp);
 983                 slotp->sl_bp = NULL;
 984         }
 985         return (ENOENT);
 986 }
 987 
 988 /*
 989  * Obtain the block at offset "offset" in file pcp.
 990  */
 991 int
 992 pc_blkatoff(
 993         struct pcnode *pcp,
 994         offset_t offset,
 995         struct buf **bpp,
 996         struct pcdir **epp)
 997 {
 998         struct pcfs *fsp;
 999         struct buf *bp;
1000         int size;
1001         int error;
1002         daddr_t bn;
1003 
1004         fsp = VFSTOPCFS(PCTOV(pcp)->v_vfsp);
1005         size = pc_blksize(fsp, pcp, offset);
1006         if (pc_blkoff(fsp, offset) >= size) {
1007                 PC_DPRINTF0(5, "pc_blkatoff: ENOENT\n");
1008                 return (ENOENT);
1009         }
1010         error = pc_bmap(pcp, pc_lblkno(fsp, offset), &bn, (uint_t *)0);
1011         if (error)
1012                 return (error);
1013 
1014         bp = bread(fsp->pcfs_xdev, bn, size);
1015         if (bp->b_flags & B_ERROR) {
1016                 PC_DPRINTF0(1, "pc_blkatoff: error\n");
1017                 brelse(bp);
1018                 pc_mark_irrecov(fsp);
1019                 return (EIO);
1020         }
1021         if (epp) {
1022                 *epp =
1023                     (struct pcdir *)(bp->b_un.b_addr + pc_blkoff(fsp, offset));
1024         }
1025         *bpp = bp;
1026         return (0);
1027 }
1028 
1029 /*
1030  * Parse user filename into the pc form of "filename.extension".
1031  * If names are too long for the format (and enable_long_filenames is set)
1032  * it returns EINVAL (since either this name was read from the disk (so
1033  * it must fit), _or_ we're trying to match a long file name (so we
1034  * should fail).  Tests for characters that are invalid in PCDOS and
1035  * converts to upper case (unless foldcase is 0).
1036  */
1037 static int
1038 pc_parsename(
1039         char *namep,
1040         char *fnamep,
1041         char *fextp)
1042 {
1043         int n;
1044         char c;
1045 
1046         n = PCFNAMESIZE;
1047         c = *namep++;
1048         if (c == 0)
1049                 return (EINVAL);
1050         if (c == '.') {
1051                 /*
1052                  * check for "." and "..".
1053                  */
1054                 *fnamep++ = c;
1055                 n--;
1056                 if (c = *namep++) {
1057                         if ((c != '.') || (c = *namep)) /* ".x" or "..x" */
1058                                 return (EINVAL);
1059                         *fnamep++ = '.';
1060                         n--;
1061                 }
1062         } else {
1063                 /*
1064                  * filename up to '.'
1065                  */
1066                 do {
1067                         if (n-- > 0) {
1068                                 c = toupper(c);
1069                                 if (!pc_validchar(c))
1070                                         return (EINVAL);
1071                                 *fnamep++ = c;
1072                         } else {
1073                                 /* not short */
1074                                 if (enable_long_filenames)
1075                                         return (EINVAL);
1076                         }
1077                 } while ((c = *namep++) != '\0' && c != '.');
1078         }
1079         while (n-- > 0) {            /* fill with blanks */
1080                 *fnamep++ = ' ';
1081         }
1082         /*
1083          * remainder is extension
1084          */
1085         n = PCFEXTSIZE;
1086         if (c == '.') {
1087                 while ((c = *namep++) != '\0' && n--) {
1088                         c = toupper(c);
1089                         if (!pc_validchar(c))
1090                                 return (EINVAL);
1091                         *fextp++ = c;
1092                 }
1093                 if (enable_long_filenames && (c != '\0')) {
1094                         /* not short */
1095                         return (EINVAL);
1096                 }
1097         }
1098         while (n-- > 0) {            /* fill with blanks */
1099                 *fextp++ = ' ';
1100         }
1101         return (0);
1102 }
1103 
1104 /*
1105  * Match a long filename entry with 'namep'. Also return failure
1106  * if the long filename isn't valid.
1107  */
1108 int
1109 pc_match_long_fn(struct pcnode *pcp, char *namep, struct pcdir **epp,
1110     struct pcslot *slotp, offset_t *offset)
1111 {
1112         struct pcdir *ep = (struct pcdir *)*epp;
1113         struct vnode *vp = PCTOV(pcp);
1114         struct pcfs *fsp = VFSTOPCFS(vp->v_vfsp);
1115         int     error = 0;
1116         char    lfn[PCMAXNAMLEN+1];
1117 
1118         error = pc_extract_long_fn(pcp, lfn, epp, offset, &slotp->sl_bp);
1119         if (error) {
1120                 if (error == EINVAL) {
1121                         return (ENOENT);
1122                 } else
1123                         return (error);
1124         }
1125         ep = *epp;
1126         if ((u8_strcmp(lfn, namep, 0, U8_STRCMP_CI_UPPER,
1127             U8_UNICODE_LATEST, &error) == 0) && (error == 0)) {
1128                 /* match */
1129                 slotp->sl_flags = 0;
1130                 slotp->sl_blkno = pc_daddrdb(fsp, slotp->sl_bp->b_blkno);
1131                 slotp->sl_offset = pc_blkoff(fsp, *offset);
1132                 slotp->sl_ep = ep;
1133                 return (0);
1134         }
1135         *offset += sizeof (struct pcdir);
1136         ep++;
1137         *epp = ep;
1138         /* If u8_strcmp detected an error it's sufficient to rtn ENOENT */
1139         return (ENOENT);
1140 }
1141 
1142 /*
1143  * Match a short filename entry with namep.
1144  */
1145 int
1146 pc_match_short_fn(struct pcnode *pcp, char *namep, struct pcdir **epp,
1147     struct pcslot *slotp, offset_t *offset)
1148 {
1149         char fname[PCFNAMESIZE];
1150         char fext[PCFEXTSIZE];
1151         struct pcdir *ep = *epp;
1152         int     error;
1153         struct vnode *vp = PCTOV(pcp);
1154         struct pcfs *fsp = VFSTOPCFS(vp->v_vfsp);
1155         int boff = pc_blkoff(fsp, *offset);
1156 
1157         if (PCA_IS_HIDDEN(fsp, ep->pcd_attr)) {
1158                 *offset += sizeof (struct pcdir);
1159                 ep++;
1160                 *epp = ep;
1161                 return (ENOENT);
1162         }
1163 
1164         error = pc_parsename(namep, fname, fext);
1165         if (error) {
1166                 *offset += sizeof (struct pcdir);
1167                 ep++;
1168                 *epp = ep;
1169                 return (error);
1170         }
1171 
1172         if ((bcmp(fname, ep->pcd_filename, PCFNAMESIZE) == 0) &&
1173             (bcmp(fext, ep->pcd_ext, PCFEXTSIZE) == 0)) {
1174                 /*
1175                  * found the file
1176                  */
1177                 if (fname[0] == '.') {
1178                         if (fname[1] == '.')
1179                                 slotp->sl_flags = SL_DOTDOT;
1180                         else
1181                                 slotp->sl_flags = SL_DOT;
1182                 } else {
1183                         slotp->sl_flags = 0;
1184                 }
1185                 slotp->sl_blkno =
1186                     pc_daddrdb(fsp, slotp->sl_bp->b_blkno);
1187                 slotp->sl_offset = boff;
1188                 slotp->sl_ep = ep;
1189                 return (0);
1190         }
1191         *offset += sizeof (struct pcdir);
1192         ep++;
1193         *epp = ep;
1194         return (ENOENT);
1195 }
1196 
1197 /*
1198  * Remove a long filename entry starting at lfn_offset. It must be
1199  * a valid entry or we wouldn't have gotten here. Also remove the
1200  * short filename entry.
1201  */
1202 static int
1203 pc_remove_long_fn(struct pcnode *pcp, offset_t lfn_offset)
1204 {
1205         struct vnode *vp = PCTOV(pcp);
1206         struct pcfs *fsp = VFSTOPCFS(vp->v_vfsp);
1207         int boff;
1208         struct buf *bp = NULL;
1209         struct pcdir *ep = NULL;
1210         int     error = 0;
1211 
1212         /*
1213          * if we're in here, we know that the lfn is in the proper format
1214          * of <series-of-lfn-entries> followed by <sfn-entry>
1215          */
1216         for (;;) {
1217                 boff = pc_blkoff(fsp, lfn_offset);
1218                 if (boff == 0 || bp == NULL || boff >= bp->b_bcount) {
1219                         if (bp != NULL) {
1220                                 bwrite2(bp);
1221                                 error = geterror(bp);
1222                                 brelse(bp);
1223                                 if (error)
1224                                         return (error);
1225                                 bp = NULL;
1226                         }
1227                         error = pc_blkatoff(pcp, lfn_offset, &bp, &ep);
1228                         if (error)
1229                                 return (error);
1230                 }
1231                 if (!PCDL_IS_LFN(ep)) {
1232                         /* done */
1233                         break;
1234                 }
1235                 /* zap it */
1236                 ep->pcd_filename[0] = PCD_ERASED;
1237                 ep->pcd_attr = 0;
1238                 lfn_offset += sizeof (struct pcdir);
1239                 ep++;
1240         }
1241         /* now we're on the short entry */
1242 
1243         ep->pcd_filename[0] = PCD_ERASED;
1244         ep->pcd_attr = 0;
1245 
1246         if (bp != NULL) {
1247                 bwrite2(bp);
1248                 error = geterror(bp);
1249                 brelse(bp);
1250                 if (error)
1251                         return (error);
1252         }
1253         return (0);
1254 }
1255 
1256 /*
1257  * Find (and allocate) space in the directory denoted by
1258  * 'pcp'. for 'ndirentries' pcdir structures.
1259  * Return the offset at which to start, or -1 for failure.
1260  */
1261 static offset_t
1262 pc_find_free_space(struct pcnode *pcp, int ndirentries)
1263 {
1264         offset_t offset = 0;
1265         offset_t spaceneeded = ndirentries * sizeof (struct pcdir);
1266         offset_t spaceoffset;
1267         offset_t spaceavail = 0;
1268         int boff;
1269         struct buf *bp = NULL;
1270         struct vnode *vp = PCTOV(pcp);
1271         struct pcfs *fsp = VFSTOPCFS(vp->v_vfsp);
1272         struct pcdir *ep;
1273         int     error;
1274 
1275         spaceoffset = offset;
1276         while (spaceneeded > spaceavail) {
1277                 /*
1278                  * If offset is on a block boundary,
1279                  * read in the next directory block.
1280                  * Release previous if it exists.
1281                  */
1282                 boff = pc_blkoff(fsp, offset);
1283                 if (boff == 0 || bp == NULL || boff >= bp->b_bcount) {
1284                         if (bp != NULL) {
1285                                 brelse(bp);
1286                                 bp = NULL;
1287                         }
1288                         error = pc_blkatoff(pcp, offset, &bp, &ep);
1289                         if (error == ENOENT) {
1290                                 daddr_t bn;
1291 
1292                                 /* extend directory */
1293                                 if (!IS_FAT32(fsp) && (vp->v_flag & VROOT))
1294                                         return (-1);
1295                                 while (spaceneeded > spaceavail) {
1296                                         error = pc_balloc(pcp,
1297                                             pc_lblkno(fsp, offset), 1, &bn);
1298                                         if (error)
1299                                                 return (-1);
1300                                         pcp->pc_size += fsp->pcfs_clsize;
1301                                         spaceavail += fsp->pcfs_clsize;
1302                                         offset += fsp->pcfs_clsize;
1303                                 }
1304                                 return (spaceoffset);
1305                         }
1306                         if (error)
1307                                 return (-1);
1308                 }
1309                 if ((ep->pcd_filename[0] == PCD_UNUSED) ||
1310                     (ep->pcd_filename[0] == PCD_ERASED)) {
1311                         offset += sizeof (struct pcdir);
1312                         spaceavail += sizeof (struct pcdir);
1313                         ep++;
1314                         continue;
1315                 }
1316                 offset += sizeof (struct pcdir);
1317                 spaceavail = 0;
1318                 spaceoffset = offset;
1319                 ep++;
1320         }
1321         if (bp != NULL) {
1322                 brelse(bp);
1323         }
1324         return (spaceoffset);
1325 }
1326 
1327 /*
1328  * Return how many long filename entries are needed.
1329  * A maximum of PCLFNCHUNKSIZE characters per entry, plus one for a
1330  * short filename.
1331  */
1332 static int
1333 direntries_needed(struct pcnode *dp, char *namep)
1334 {
1335         struct pcdir ep;
1336         uint16_t *w2_str;
1337         size_t  u8l, u16l;
1338         int ret;
1339 
1340         if (enable_long_filenames == 0) {
1341                 return (1);
1342         }
1343         if (pc_is_short_file_name(namep, 0)) {
1344                 (void) pc_parsename(namep, ep.pcd_filename, ep.pcd_ext);
1345                 if (!shortname_exists(dp, ep.pcd_filename, ep.pcd_ext)) {
1346                         return (1);
1347                 }
1348         }
1349         if (pc_valid_long_fn(namep, 1)) {
1350                 /*
1351                  * convert to UTF-16 or UNICODE for calculating the entries
1352                  * needed. Conversion will consume at the most 512 bytes
1353                  */
1354                 u16l = PCMAXNAMLEN + 1;
1355                 w2_str = (uint16_t *)kmem_zalloc(PCMAXNAM_UTF16, KM_SLEEP);
1356                 u8l = strlen(namep);
1357                 ret = uconv_u8tou16((const uchar_t *)namep, &u8l,
1358                     w2_str, &u16l, UCONV_OUT_LITTLE_ENDIAN);
1359                 kmem_free((caddr_t)w2_str, PCMAXNAM_UTF16);
1360                 if (ret == 0) {
1361                         ret = 1 + u16l / PCLFNCHUNKSIZE;
1362                         if (u16l % PCLFNCHUNKSIZE != 0)
1363                                 ret++;
1364                         return (ret);
1365                 }
1366         }
1367         return (-1);
1368 }
1369 
1370 /*
1371  * Allocate and return an array of pcdir structures for the passed-in
1372  * name. ndirentries tells how many are required (including the short
1373  * filename entry). Just allocate and fill them in properly here so they
1374  * can be written out.
1375  */
1376 static struct pcdir *
1377 pc_name_to_pcdir(struct pcnode *dp, char *namep, int ndirentries, int *errret)
1378 {
1379         struct pcdir *bpcdir;
1380         struct pcdir *ep;
1381         struct pcdir_lfn *lep;
1382         int     i;
1383         uchar_t cksum;
1384         int     nchars;
1385         int     error = 0;
1386         char    *nameend;
1387         uint16_t *w2_str;
1388         size_t  u8l, u16l;
1389         int ret;
1390 
1391         bpcdir = kmem_zalloc(ndirentries * sizeof (struct pcdir), KM_SLEEP);
1392         ep = &bpcdir[ndirentries - 1];
1393         if (ndirentries == 1) {
1394                 (void) pc_parsename(namep, ep->pcd_filename, ep->pcd_ext);
1395                 return (bpcdir);
1396         }
1397 
1398         /* Here we need to convert to UTF-16 or UNICODE for writing */
1399 
1400         u16l = PCMAXNAMLEN + 1;
1401         w2_str = (uint16_t *)kmem_zalloc(PCMAXNAM_UTF16, KM_SLEEP);
1402         u8l = strlen(namep);
1403         ret = uconv_u8tou16((const uchar_t *)namep, &u8l, w2_str, &u16l,
1404             UCONV_OUT_LITTLE_ENDIAN);
1405         if (ret != 0) {
1406                 kmem_free((caddr_t)w2_str, PCMAXNAM_UTF16);
1407                 *errret = ret;
1408                 return (NULL);
1409         }
1410         nameend = (char *)(w2_str + u16l);
1411         u16l %= PCLFNCHUNKSIZE;
1412         if (u16l != 0) {
1413                 nchars = u16l + 1;
1414                 nameend += 2;
1415         } else {
1416                 nchars = PCLFNCHUNKSIZE;
1417         }
1418         nchars *= sizeof (uint16_t);
1419 
1420         /* short file name */
1421         error = generate_short_name(dp, namep, ep);
1422         if (error) {
1423                 kmem_free(bpcdir, ndirentries * sizeof (struct pcdir));
1424                 *errret = error;
1425                 return (NULL);
1426         }
1427         cksum = pc_checksum_long_fn(ep->pcd_filename, ep->pcd_ext);
1428         for (i = 0; i < (ndirentries - 1); i++) {
1429                 /* long file name */
1430                 nameend -= nchars;
1431                 lep = (struct pcdir_lfn *)&bpcdir[i];
1432                 set_long_fn_chunk(lep, nameend, nchars);
1433                 lep->pcdl_attr = PCDL_LFN_BITS;
1434                 lep->pcdl_checksum = cksum;
1435                 lep->pcdl_ordinal = (uchar_t)(ndirentries - i - 1);
1436                 nchars = PCLFNCHUNKSIZE * sizeof (uint16_t);
1437         }
1438         kmem_free((caddr_t)w2_str, PCMAXNAM_UTF16);
1439         lep = (struct pcdir_lfn *)&bpcdir[0];
1440         lep->pcdl_ordinal |= 0x40;
1441         return (bpcdir);
1442 }
1443 
1444 static int
1445 generate_short_name(struct pcnode *dp, char *namep, struct pcdir *inep)
1446 {
1447         int     rev;
1448         int     nchars;
1449         int     i, j;
1450         char    *dot = NULL;
1451         char    fname[PCFNAMESIZE+1];
1452         char    fext[PCFEXTSIZE+1];
1453         char    scratch[8];
1454         int     error = 0;
1455         struct  pcslot slot;
1456         char    shortname[20];
1457         int     force_tilde = 0;
1458 
1459         /*
1460          * generate a unique short file name based on the long input name.
1461          *
1462          * Say, for "This is a very long filename.txt" generate
1463          * "THISIS~1.TXT", or "THISIS~2.TXT" if that's already there.
1464          * Skip invalid short name characters in the long name, plus
1465          * a couple NT skips (space and reverse backslash).
1466          *
1467          * Unfortunately, since this name would be hidden by the normal
1468          * lookup routine, we need to look for it ourselves. But luckily
1469          * we don't need to look at the lfn entries themselves.
1470          */
1471         force_tilde = !pc_is_short_file_name(namep, 1);
1472 
1473         /*
1474          * Strip off leading invalid characters.
1475          * We need this because names like '.login' are now ok, but the
1476          * short name needs to be something like LOGIN~1.
1477          */
1478         for (; *namep != '\0'; namep++) {
1479                 if (*namep == ' ')
1480                         continue;
1481                 if (!pc_validchar(*namep) && !pc_validchar(toupper(*namep)))
1482                         continue;
1483                 break;
1484         }
1485         dot = strrchr(namep, '.');
1486         if (dot != NULL) {
1487                 dot++;
1488                 for (j = 0, i = 0; j < PCFEXTSIZE; i++) {
1489                         if (dot[i] == '\0')
1490                                 break;
1491                         /* skip valid, but not generally good characters */
1492                         if (dot[i] == ' ' || dot[i] == '\\')
1493                                 continue;
1494                         if (pc_validchar(dot[i]))
1495                                 fext[j++] = dot[i];
1496                         else if (pc_validchar(toupper(dot[i])))
1497                                 fext[j++] = toupper(dot[i]);
1498                 }
1499                 for (i = j; i < PCFEXTSIZE; i++)
1500                         fext[i] = ' ';
1501                 dot--;
1502         } else {
1503                 for (i = 0; i < PCFEXTSIZE; i++) {
1504                         fext[i] = ' ';
1505                 }
1506         }
1507         /*
1508          * We know we're a long name, not a short name (or we wouldn't
1509          * be here at all. But if uppercasing ourselves would be a short
1510          * name, then we can possibly avoid the ~N format.
1511          */
1512         if (!force_tilde)
1513                 rev = 0;
1514         else
1515                 rev = 1;
1516         for (;;) {
1517                 bzero(fname, sizeof (fname));
1518                 nchars = PCFNAMESIZE;
1519                 if (rev) {
1520                         nchars--; /* ~ */
1521                         i = rev;
1522                         do {
1523                                 nchars--;
1524                                 i /= 10;
1525                         } while (i);
1526                         if (nchars <= 0) {
1527                                 return (ENOSPC);
1528                         }
1529                 }
1530                 for (j = 0, i = 0; j < nchars; i++) {
1531                         if ((&namep[i] == dot) || (namep[i] == '\0'))
1532                                 break;
1533                         /* skip valid, but not generally good characters */
1534                         if (namep[i] == ' ' || namep[i] == '\\')
1535                                 continue;
1536                         if (pc_validchar(namep[i]))
1537                                 fname[j++] = namep[i];
1538                         else if (pc_validchar(toupper(namep[i])))
1539                                 fname[j++] = toupper(namep[i]);
1540                 }
1541                 if (rev) {
1542                         (void) sprintf(scratch, "~%d", rev);
1543                         (void) strcat(fname, scratch);
1544                 }
1545                 for (i = strlen(fname); i < PCFNAMESIZE; i++)
1546                         fname[i] = ' ';
1547                 /* now see if it exists */
1548                 (void) pc_fname_ext_to_name(shortname, fname, fext, 0);
1549                 error = pc_findentry(dp, shortname, &slot, NULL);
1550                 if (error == 0) {
1551                         /* found it */
1552                         brelse(slot.sl_bp);
1553                         rev++;
1554                         continue;
1555                 }
1556                 if (!shortname_exists(dp, fname, fext))
1557                         break;
1558                 rev++;
1559         }
1560         (void) strncpy(inep->pcd_filename, fname, PCFNAMESIZE);
1561         (void) strncpy(inep->pcd_ext, fext, PCFEXTSIZE);
1562         return (0);
1563 }
1564 
1565 /*
1566  * Returns 1 if the passed-in filename is a short name, 0 if not.
1567  */
1568 static int
1569 pc_is_short_file_name(char *namep, int foldcase)
1570 {
1571         int     i;
1572         char    c;
1573 
1574         for (i = 0; i < PCFNAMESIZE; i++, namep++) {
1575                 if (*namep == '\0')
1576                         return (1);
1577                 if (*namep == '.')
1578                         break;
1579                 if (foldcase)
1580                         c = toupper(*namep);
1581                 else
1582                         c = *namep;
1583                 if (!pc_validchar(c))
1584                         return (0);
1585         }
1586         if (*namep == '\0')
1587                 return (1);
1588         if (*namep != '.')
1589                 return (0);
1590         namep++;
1591         for (i = 0; i < PCFEXTSIZE; i++, namep++) {
1592                 if (*namep == '\0')
1593                         return (1);
1594                 if (foldcase)
1595                         c = toupper(*namep);
1596                 else
1597                         c = *namep;
1598                 if (!pc_validchar(c))
1599                         return (0);
1600         }
1601         /* we should be done. If not... */
1602         if (*namep == '\0')
1603                 return (1);
1604         return (0);
1605 
1606 }
1607 
1608 /*
1609  * We call this when we want to see if a short filename already exists
1610  * in the filesystem as part of a long filename. When creating a short
1611  * name (FILENAME.TXT from the user, or when generating one for a long
1612  * filename), we cannot allow one that is part of a long filename.
1613  * pc_findentry will find all the names that are visible (long or short),
1614  * but will not crack any long filename entries.
1615  */
1616 static int
1617 shortname_exists(struct pcnode *dp, char *fname, char *fext)
1618 {
1619         struct buf *bp = NULL;
1620         int     offset = 0;
1621         int     match = 0;
1622         struct pcdir *ep;
1623         struct vnode *vp = PCTOV(dp);
1624         struct pcfs *fsp = VFSTOPCFS(vp->v_vfsp);
1625         int     boff;
1626         int     error = 0;
1627 
1628         for (;;) {
1629                 boff = pc_blkoff(fsp, offset);
1630                 if (boff == 0 || bp == NULL || boff >= bp->b_bcount) {
1631                         if (bp != NULL) {
1632                                 brelse(bp);
1633                                 bp = NULL;
1634                         }
1635                         error = pc_blkatoff(dp, offset, &bp, &ep);
1636                         if (error == ENOENT)
1637                                 break;
1638                         if (error) {
1639                                 return (1);
1640                         }
1641                 }
1642                 if (PCDL_IS_LFN(ep) ||
1643                     (ep->pcd_filename[0] == PCD_ERASED)) {
1644                         offset += sizeof (struct pcdir);
1645                         ep++;
1646                         continue;
1647                 }
1648                 if (ep->pcd_filename[0] == PCD_UNUSED)
1649                         break;
1650                 /*
1651                  * in use, and a short file name (either standalone
1652                  * or associated with a long name
1653                  */
1654                 if ((bcmp(fname, ep->pcd_filename, PCFNAMESIZE) == 0) &&
1655                     (bcmp(fext, ep->pcd_ext, PCFEXTSIZE) == 0)) {
1656                         match = 1;
1657                         break;
1658                 }
1659                 offset += sizeof (struct pcdir);
1660                 ep++;
1661         }
1662         if (bp) {
1663                 brelse(bp);
1664                 bp = NULL;
1665         }
1666         return (match);
1667 }
1668 
1669 pc_cluster32_t
1670 pc_getstartcluster(struct pcfs *fsp, struct pcdir *ep)
1671 {
1672         if (IS_FAT32(fsp)) {
1673                 pc_cluster32_t cn;
1674                 pc_cluster16_t hi16;
1675                 pc_cluster16_t lo16;
1676 
1677                 hi16 = ltohs(ep->un.pcd_scluster_hi);
1678                 lo16 = ltohs(ep->pcd_scluster_lo);
1679                 cn = (hi16 << 16) | lo16;
1680                 return (cn);
1681         } else {
1682                 return (ltohs(ep->pcd_scluster_lo));
1683         }
1684 }
1685 
1686 void
1687 pc_setstartcluster(struct pcfs *fsp, struct pcdir *ep, pc_cluster32_t cln)
1688 {
1689         if (IS_FAT32(fsp)) {
1690                 pc_cluster16_t hi16;
1691                 pc_cluster16_t lo16;
1692 
1693                 hi16 = (cln >> 16) & 0xFFFF;
1694                 lo16 = cln & 0xFFFF;
1695                 ep->un.pcd_scluster_hi = htols(hi16);
1696                 ep->pcd_scluster_lo = htols(lo16);
1697         } else {
1698                 pc_cluster16_t cln16;
1699 
1700                 cln16 = (pc_cluster16_t)cln;
1701                 ep->pcd_scluster_lo = htols(cln16);
1702         }
1703 }