/* * Copyright (c) 2000 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* $FreeBSD: src/sys/msdosfs/msdosfs_lookup.c,v 1.31 2000/05/05 09:58:35 phk Exp $ */ /* $NetBSD: msdosfs_lookup.c,v 1.37 1997/11/17 15:36:54 ws Exp $ */ /*- * Copyright (C) 1994, 1995, 1997 Wolfgang Solfrank. * Copyright (C) 1994, 1995, 1997 TooLs GmbH. * All rights reserved. * Original code by Paul Popelka (paulp@uts.amdahl.com) (see below). * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by TooLs GmbH. * 4. The name of TooLs GmbH may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY TOOLS GMBH ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL TOOLS GMBH BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Written by Paul Popelka (paulp@uts.amdahl.com) * * You can do anything you want with this software, just don't say you wrote * it, and don't remove this notice. * * This software is provided "as is". * * The author supplies this software to be publicly redistributed on the * understanding that the author is not responsible for the correct * functioning of this software in any circumstances and is not liable for * any damages caused by this software. * * October 1992 */ #include #include #include #include #include #include #include #include "bpb.h" #include "direntry.h" #include "denode.h" #include "msdosfsmount.h" #include "fat.h" /* * Try to find the file/directory in the name cache. If not * found there, then look in the directory on disk. * * When we search a directory the blocks containing directory entries are * read and examined. The directory entries contain information that would * normally be in the inode of a unix filesystem. This means that some of * a directory's contents may also be in memory resident denodes (sort of * an inode). This can cause problems if we are searching while some other * process is modifying a directory. To prevent one process from accessing * incompletely modified directory information we depend upon being the * sole owner of a directory block. buf_bread/buf_brelse provide this service. * This being the case, when a process modifies a directory it must first * acquire the disk block that contains the directory entry to be modified. * Then update the disk block and the denode, and then write the disk block * out to disk. This way disk blocks containing directory entries and in * memory denode's will be in synch. */ __private_extern__ int msdosfs_lookup(ap) struct vnop_lookup_args /* { vnode_t a_dvp; vnode_t *a_vpp; struct componentname *a_cnp; vfs_context_t a_context; } */ *ap; { vnode_t dvp = ap->a_dvp; vnode_t *vpp = ap->a_vpp; struct componentname *cnp = ap->a_cnp; vfs_context_t context = ap->a_context; int flags = cnp->cn_flags; int nameiop = cnp->cn_nameiop; int error; struct msdosfsmount *pmp; struct denode *pdp; /* denode of dvp */ struct denode *dp; /* denode of found item */ struct dosdirentry *dep; buf_t bp; daddr64_t bn; int frcn; /* file relative cluster (within parent directory) */ u_long cluster; /* physical cluster containing directory entry */ int blkoff; /* offset within directory block */ int diroff; /* offset from start of directory */ u_long blsize; /* size of one directory block */ u_long scn; /* starting cluster number of found item */ int isadir; /* non-zero if found dosdirentry is a directory */ int chksum; /* checksum of short name entry */ int olddos; u_int16_t ucfn[WIN_MAXLEN]; size_t unichars; /* number of UTF-16 characters in original name */ u_int8_t lower_case; /* deLowerCase value; not used in this routine */ u_char dosfilename[12]; *vpp = NULL; /* In case we return an error */ error = cache_lookup(dvp, vpp, cnp); if (error) { /* We found a cache entry, positive or negative, so return it. */ if (error == -1) error = 0; /* No error on positive match */ return error; } /* If we get here, we need to look for the item on disk. */ pdp = VTODE(dvp); pmp = pdp->de_pmp; dep = NULL; chksum = -1; olddos = 1; /* * If they are going after the . or .. entry in the root directory, * they won't find it. DOS filesystems don't have them in the root * directory. So, we fake it. deget() is in on this scam too. */ if (vnode_isvroot(dvp) && cnp->cn_nameptr[0] == '.' && (cnp->cn_namelen == 1 || (cnp->cn_namelen == 2 && cnp->cn_nameptr[1] == '.'))) { isadir = ATTR_DIRECTORY; scn = MSDOSFSROOT; cluster = MSDOSFSROOT; diroff = MSDOSFSROOT_OFS; goto foundroot; } /* * Decode lookup name into UCS-2 (Unicode) */ error = utf8_decodestr(cnp->cn_nameptr, cnp->cn_namelen, ucfn, &unichars, sizeof(ucfn), 0, UTF_PRECOMPOSED); if (error) goto exit; unichars /= 2; /* bytes to chars */ switch (unicode2dosfn(ucfn, dosfilename, unichars, 0, &lower_case)) { case 0: /* * The name is syntactically invalid. Normally, we'd return EINVAL, * but ENAMETOOLONG makes it clear that the name is the problem (and * allows Carbon to return a more meaningful error). */ return (ENAMETOOLONG); case 1: break; case 2: break; case 3: olddos = 0; break; } /* Convert the Mac unicode string to SFM unicode * We do this after unicode2dosfn to avoid interpreting 0xf0xx * unicodes in unicode2dosfn */ mac2sfmfn(ucfn, unichars); /* * Search the directory pointed at by dvp for the name in ucfn. */ dp = NULL; /* * The outer loop ranges over the clusters that make up the * directory. Note that the root directory is different from all * other directories. It has a fixed number of blocks that are not * part of the pool of allocatable clusters. So, we treat it a * little differently. The root directory starts at "cluster" 0. */ diroff = 0; for (frcn = 0;; frcn++) { error = pcbmap(pdp, frcn, 1, &bn, &cluster, &blsize, context); if (error) { if (error == E2BIG) break; goto exit; } error = (int)buf_meta_bread(pmp->pm_devvp, bn, blsize, vfs_context_ucred(context), &bp); if (error) { buf_brelse(bp); goto exit; } for (blkoff = 0; blkoff < blsize; blkoff += sizeof(struct dosdirentry), diroff += sizeof(struct dosdirentry)) { dep = (struct dosdirentry *)((char *)buf_dataptr(bp) + blkoff); /* * If the slot is empty and we are still looking * for an empty then remember this one. If the * slot is not empty then check to see if it * matches what we are looking for. If the slot * has never been filled with anything, then the * remainder of the directory has never been used, * so there is no point in searching it. */ if (dep->deName[0] == SLOT_EMPTY || dep->deName[0] == SLOT_DELETED) { /* * Drop memory of previous long matches */ chksum = -1; /* * If we found SLOT_EMPTY, then we've reached a part of * the directory that has never been used, so we can * stop early. */ if (dep->deName[0] == SLOT_EMPTY) { buf_brelse(bp); goto notfound; } } else { /* * Check for Win95 long filename entry */ if (dep->deAttributes == ATTR_WIN95) { chksum = winChkName(ucfn, unichars, (struct winentry *)dep, chksum); continue; } /* * Ignore volume labels (anywhere, not just * the root directory). */ if (dep->deAttributes & ATTR_VOLUME) { chksum = -1; continue; } /* * Check for a checksum or name match */ if (chksum != winChksum(dep->deName) && (!olddos || bcmp(dosfilename, dep->deName, 11))) { chksum = -1; continue; } goto found; } } /* for (blkoff = 0; .... */ /* * Release the buffer holding the directory cluster just * searched. */ buf_brelse(bp); } /* for (frcn = 0; ; frcn++) */ notfound: /* * We hold no disk buffers at this point. */ /* * If we get here we didn't find the entry we were looking for. But * that's ok if we are creating or renaming and are at the end of * the pathname and the directory hasn't been removed. */ if ((nameiop == CREATE || nameiop == RENAME) && (flags & ISLASTCN) && pdp->de_refcnt != 0) { error = EJUSTRETURN; goto exit; } /* * Insert name into cache (as non-existent) if appropriate. */ if ((cnp->cn_flags & MAKEENTRY) && nameiop != CREATE) cache_enter(dvp, *vpp, cnp); error = ENOENT; goto exit; found: /* * NOTE: We still have the buffer with matched directory entry at * this point. */ isadir = dep->deAttributes & ATTR_DIRECTORY; scn = getushort(dep->deStartCluster); if (FAT32(pmp)) { scn |= getushort(dep->deHighClust) << 16; if (scn == pmp->pm_rootdirblk) { /* * There should actually be 0 here. * Just ignore the error. */ scn = MSDOSFSROOT; } } if (isadir) { /* * For directories, the cluster and offset in the denode * actually point at the "." entry in the directory. * The denode for the root directory itself uses a special * constant MSDOSFSROOT_OFS for the offset. */ cluster = scn; if (cluster == MSDOSFSROOT) diroff = MSDOSFSROOT_OFS; else diroff = 0; } /* * Now release buf to allow deget to read the entry again. * Reserving it here and giving it to deget could result * in a deadlock. */ buf_brelse(bp); bp = 0; foundroot: /* * If we entered at foundroot, then we are looking for the . or .. * entry of the filesystems root directory. isadir and scn were * setup before jumping here. And, bp is already null. */ if (FAT32(pmp) && scn == MSDOSFSROOT) scn = pmp->pm_rootdirblk; /* * If deleting, and at end of pathname, return * parameters which can be used to remove file. */ if (nameiop == DELETE && (flags & ISLASTCN)) { /* * Don't allow deleting the root. */ if (diroff == MSDOSFSROOT_OFS) { error = EROFS; /* correct error? */ goto exit; } /* * Return pointer to current entry in pdp->i_offset. * Save directory inode pointer in ndp->ni_dvp for dirremove(). */ if (pdp->de_StartCluster == scn && isadir) { /* "." */ *vpp = dvp; error = 0; goto exit; } } /* * If rewriting (RENAME), return the inode and the * information required to rewrite the present directory * Must get inode of directory entry to verify it's a * regular file, or empty directory. */ if (nameiop == RENAME && (flags & ISLASTCN)) { if (diroff == MSDOSFSROOT_OFS) { error = EROFS; /* really? XXX */ goto exit; } if (pdp->de_StartCluster == scn && isadir) { error = EISDIR; goto exit; } } error = deget(pmp, cluster, diroff, dvp, cnp, &dp, context); if (error == 0) *vpp = DETOV(dp); exit: return error; } /* * Find the offset of the directory entry for a directory within * its parent directory. * * A directory is normally identified by its starting cluster * and an offset of 0 (i.e. its "." own entry). But to update * the directory entry in the parent (for delete, rename, etc.), * we need to know its offset within the parent. Normally, the * preceding lookup set de_fndoffset in the parent directory. * But in the case of rename, the lookup of the target (destination) * can overwrite de_fndoffset if the source and target are in * the same directory. * * What we do is read through the parent directory, looking for * a directory whose start cluster matches the child directory's * start cluster. */ __private_extern__ int msdosfs_lookupdir(ddep, dep, offset, context) struct denode *ddep; /* Parent directory */ struct denode *dep; /* Child (directory) */ u_int32_t *offset; /* Offset of child's entry within parent */ vfs_context_t context; { struct msdosfsmount *pmp; buf_t bp; struct dosdirentry *ep; daddr64_t bn; int error = 0; u_long cn; /* Absolute cluster number */ u_int32_t frcn; /* Directory-relative cluster */ u_int32_t blkoffset; /* Byte offset from start of cluster */ u_int32_t diroffset; /* Byte offset from start of directory */ u_long blsize; /* Size of block/cluster */ pmp = ddep->de_pmp; diroffset = 0; for (frcn=0; ; frcn++) { error = pcbmap(ddep, frcn, 1, &bn, &cn, &blsize, context); if (error) goto exit; error = buf_meta_bread(pmp->pm_devvp, bn, blsize, vfs_context_ucred(context), &bp); if (error) goto release; for (blkoffset = 0; blkoffset < blsize; blkoffset += sizeof(struct dosdirentry)) { ep = (struct dosdirentry *) ((char *)buf_dataptr(bp) + blkoffset); if (ep->deName[0] == SLOT_EMPTY) { panic("msdosfs_lookupdir: child not found"); error = ENOENT; goto release; } if (ep->deName[0] == SLOT_DELETED) continue; if (ep->deAttributes == ATTR_WIN95) continue; if (ep->deAttributes & ATTR_DIRECTORY) { cn = getushort(ep->deStartCluster); if (FAT32(pmp)) cn |= getushort(ep->deHighClust) << 16; if (cn == dep->de_StartCluster) { *offset = diroffset + blkoffset; goto release; } } } buf_brelse(bp); diroffset += blsize; } release: buf_brelse(bp); exit: return error; } /* * dep - directory entry to copy into the directory * ddep - directory to add to * depp - return the address of the denode for the created directory entry * if depp != 0 * cnp - componentname needed for Win95 long filenames * offset - directory offset for short name entry * long_count - number of long name entries needed */ __private_extern__ int createde(dep, ddep, depp, cnp, offset, long_count, context) struct denode *dep; struct denode *ddep; struct denode **depp; struct componentname *cnp; u_long offset; /* also offset of current entry being written */ u_long long_count; /* also count of entries remaining to write */ vfs_context_t context; { int error; u_long dirclust, diroffset; struct dosdirentry *ndep; struct msdosfsmount *pmp = ddep->de_pmp; struct buf *bp; daddr64_t bn; u_long blsize; /* * If no space left in the directory then allocate another cluster * and chain it onto the end of the file. There is one exception * to this. That is, if the root directory has no more space it * can NOT be expanded. extendfile() checks for and fails attempts * to extend the root directory. We just return an error in that * case. */ if (offset >= ddep->de_FileSize) { diroffset = offset + sizeof(struct dosdirentry) - ddep->de_FileSize; dirclust = de_clcount(pmp, diroffset); error = extendfile(ddep, dirclust, context); if (error) { (void)detrunc(ddep, ddep->de_FileSize, 0, context); return error; } /* * Update the size of the directory */ ddep->de_FileSize += de_cn2off(pmp, dirclust); } /* * We just read in the cluster with space. Copy the new directory * entry in. Then write it to disk. NOTE: DOS directories * do not get smaller as clusters are emptied. */ error = pcbmap(ddep, de_cluster(pmp, offset), 1, &bn, &dirclust, &blsize, context); if (error) return error; diroffset = offset; if ((error = (int)buf_meta_bread(pmp->pm_devvp, bn, blsize, vfs_context_ucred(context), &bp)) != 0) { buf_brelse(bp); return error; } ndep = bptoep(pmp, bp, offset); DE_EXTERNALIZE(ndep, dep); /* * Now write the Win95 long name */ if (long_count > 0) { u_int8_t chksum = winChksum(ndep->deName); u_int16_t ucfn[WIN_MAXLEN]; size_t unichars; int cnt = 1; /* * Decode component name into Unicode * NOTE: We should be using a "precompose" flag */ (void) utf8_decodestr(cnp->cn_nameptr, cnp->cn_namelen, ucfn, &unichars, sizeof(ucfn), 0, UTF_PRECOMPOSED); unichars /= 2; /* bytes to chars */ /* Convert the Mac unicode string to SFM unicode */ mac2sfmfn(ucfn, unichars); while (--long_count >= 0) { if (!(offset & pmp->pm_crbomask)) { error = (int)buf_bwrite(bp); if (error) return error; offset -= sizeof(struct dosdirentry); error = pcbmap(ddep, de_cluster(pmp, offset), 1, &bn, 0, &blsize, context); if (error) return error; error = (int)buf_meta_bread(pmp->pm_devvp, bn, blsize, vfs_context_ucred(context), &bp); if (error) { buf_brelse(bp); return error; } ndep = bptoep(pmp, bp, offset); } else { ndep--; offset -= sizeof(struct dosdirentry); } if (!unicode2winfn(ucfn, unichars, (struct winentry *)ndep, cnt++, chksum)) break; } } error = (int)buf_bwrite(bp); if (error) return error; ddep->de_flag |= DE_UPDATE; /* * If they want us to return with the denode gotten. */ if (depp) { if (dep->de_Attributes & ATTR_DIRECTORY) { dirclust = dep->de_StartCluster; if (FAT32(pmp) && dirclust == pmp->pm_rootdirblk) dirclust = MSDOSFSROOT; if (dirclust == MSDOSFSROOT) diroffset = MSDOSFSROOT_OFS; else diroffset = 0; } return deget(pmp, dirclust, diroffset, DETOV(ddep), cnp, depp, context); } return 0; } /* * Be sure a directory is empty except for "." and "..". Return 1 if empty, * return 0 if not empty or error. */ __private_extern__ int dosdirempty(dep, context) struct denode *dep; vfs_context_t context; { u_long blsize; int error; u_long cn; daddr64_t bn; struct buf *bp; struct msdosfsmount *pmp = dep->de_pmp; struct dosdirentry *dentp; char *bdata; /* * Since the filesize field in directory entries for a directory is * zero, we just have to feel our way through the directory until * we hit end of file. */ for (cn = 0;; cn++) { if ((error = pcbmap(dep, cn, 1, &bn, NULL, &blsize, context)) != 0) { if (error == E2BIG) return (1); /* it's empty */ return (0); } error = (int)buf_meta_bread(pmp->pm_devvp, bn, blsize, vfs_context_ucred(context), &bp); if (error) { buf_brelse(bp); return (0); } bdata = (char *)buf_dataptr(bp); for (dentp = (struct dosdirentry *)bdata; (char *)dentp < bdata + blsize; dentp++) { if (dentp->deName[0] != SLOT_DELETED && (dentp->deAttributes & ATTR_VOLUME) == 0) { /* * In dos directories an entry whose name * starts with SLOT_EMPTY (0) starts the * beginning of the unused part of the * directory, so we can just return that it * is empty. */ if (dentp->deName[0] == SLOT_EMPTY) { buf_brelse(bp); return (1); } /* * Any names other than "." and ".." in a * directory mean it is not empty. */ if (bcmp(dentp->deName, ". ", 11) && bcmp(dentp->deName, ".. ", 11)) { buf_brelse(bp); return (0); /* not empty */ } } } buf_brelse(bp); } /* NOTREACHED */ } /* * Check to see if the directory described by target is in some * subdirectory of source. This prevents something like the following from * succeeding and leaving a bunch or files and directories orphaned. mv * /a/b/c /a/b/c/d/e/f Where c and f are directories. * * source - the inode for /a/b/c * target - the inode for /a/b/c/d/e/f * * Returns 0 if target is NOT a subdirectory of source. * Otherwise returns a non-zero error number. * The target inode is always unlocked on return. */ __private_extern__ int doscheckpath(source, target, context) struct denode *source; struct denode *target; vfs_context_t context; { daddr64_t scn; struct msdosfsmount *pmp; struct dosdirentry *ep; struct denode *dep; vnode_t put_vp = NULLVP; /* vnode which needs to be "put" */ struct buf *bp = NULL; int error = 0; char *bdata; dep = target; if ((dep->de_Attributes & ATTR_DIRECTORY) == 0 || (source->de_Attributes & ATTR_DIRECTORY) == 0) { error = ENOTDIR; goto out; } if (dep->de_StartCluster == source->de_StartCluster) { error = EEXIST; goto out; } if (dep->de_StartCluster == MSDOSFSROOT) goto out; pmp = dep->de_pmp; if (FAT32(pmp) && dep->de_StartCluster == pmp->pm_rootdirblk) goto out; for (;;) { if ((dep->de_Attributes & ATTR_DIRECTORY) == 0) { error = ENOTDIR; break; } scn = dep->de_StartCluster; error = (int)buf_meta_bread(pmp->pm_devvp, cntobn(pmp, scn), pmp->pm_bpcluster, vfs_context_ucred(context), &bp); if (error) break; bdata = (char *)buf_dataptr(bp); ep = (struct dosdirentry *) bdata + 1; if ((ep->deAttributes & ATTR_DIRECTORY) == 0 || bcmp(ep->deName, ".. ", 11) != 0) { error = ENOTDIR; break; } scn = getushort(ep->deStartCluster); if (FAT32(pmp)) scn |= getushort(ep->deHighClust) << 16; if (scn == source->de_StartCluster) { error = EINVAL; break; } if (scn == MSDOSFSROOT) break; if (FAT32(pmp) && scn == pmp->pm_rootdirblk) { /* * scn should be 0 in this case, * but we silently ignore the error. */ break; } buf_brelse(bp); bp = NULL; if (put_vp != NULLVP) { vnode_put(put_vp); put_vp = NULLVP; } /* NOTE: deget() clears dep on error */ if ((error = deget(pmp, scn, 0, NULLVP, NULL, &dep, context)) != 0) break; put_vp = DETOV(dep); } out:; if (bp) buf_brelse(bp); if (put_vp != NULLVP) vnode_put(put_vp); if (error == ENOTDIR) printf("doscheckpath(): .. not a directory?\n"); return (error); } /* * Read in the disk block containing the directory entry (dirclu, dirofs) * and return the address of the buf header, and the address of the * directory entry within the block. */ __private_extern__ int readep(pmp, dirclust, diroffset, bpp, epp, context) struct msdosfsmount *pmp; u_long dirclust, diroffset; struct buf **bpp; struct dosdirentry **epp; vfs_context_t context; { int error; daddr64_t bn; int blsize; /* * Handle the special case of the root directory. If there is a volume * label entry, then get that. Otherwise, return an error. */ if ((dirclust == MSDOSFSROOT || (FAT32(pmp) && dirclust == pmp->pm_rootdirblk)) && diroffset == MSDOSFSROOT_OFS) { if (pmp->pm_label_cluster == CLUST_EOFE) return EIO; else { dirclust = pmp->pm_label_cluster; diroffset = pmp->pm_label_offset; } } blsize = pmp->pm_bpcluster; if (dirclust == MSDOSFSROOT && de_blk(pmp, diroffset + blsize) > pmp->pm_rootdirsize) { blsize = de_bn2off(pmp, pmp->pm_rootdirsize) & pmp->pm_crbomask; } bn = detobn(pmp, dirclust, diroffset); if ((error = (int)buf_meta_bread(pmp->pm_devvp, bn, blsize, vfs_context_ucred(context), bpp)) != 0) { buf_brelse(*bpp); *bpp = NULL; return (error); } if (epp) *epp = bptoep(pmp, *bpp, diroffset); return (0); } /* * Read in the disk block containing the directory entry dep came from and * return the address of the buf header, and the address of the directory * entry within the block. */ __private_extern__ int readde(dep, bpp, epp, context) struct denode *dep; struct buf **bpp; struct dosdirentry **epp; vfs_context_t context; { return (readep(dep->de_pmp, dep->de_dirclust, dep->de_diroffset, bpp, epp, context)); } /* * Remove a directory entry. At this point the file represented by the * directory entry to be removed is still full length until noone has it * open. When the file no longer being used msdosfs_inactive() is called * and will truncate the file to 0 length. When the vnode containing the * denode is needed for some other purpose by VFS it will call * msdosfs_reclaim() which will remove the denode from the denode cache. */ __private_extern__ int removede(struct denode *pdep, uint32_t offset, vfs_context_t context) { int error; struct dosdirentry *ep; struct buf *bp; daddr64_t bn; u_long blsize; struct msdosfsmount *pmp = pdep->de_pmp; u_long cur_offset; cur_offset = offset; cur_offset += sizeof(struct dosdirentry); do { cur_offset -= sizeof(struct dosdirentry); error = pcbmap(pdep, de_cluster(pmp, cur_offset), 1, &bn, NULL, &blsize, context); if (error) return error; error = (int)buf_meta_bread(pmp->pm_devvp, bn, blsize, vfs_context_ucred(context), &bp); if (error) { buf_brelse(bp); return error; } ep = bptoep(pmp, bp, cur_offset); /* * Stop deleting long name entries when we find some other short * name entry. */ if (ep->deAttributes != ATTR_WIN95 && cur_offset != offset) { buf_brelse(bp); break; } cur_offset += sizeof(struct dosdirentry); while (1) { /* * We are a bit agressive here in that we delete any Win95 * entries preceding this entry, not just the ones we "own". * Since these presumably aren't valid anyway, * there should be no harm. */ cur_offset -= sizeof(struct dosdirentry); ep--->deName[0] = SLOT_DELETED; if ((cur_offset & pmp->pm_crbomask) == 0 || ep->deAttributes != ATTR_WIN95) break; } error = (int)buf_bwrite(bp); if (error) return error; } while ((cur_offset & pmp->pm_crbomask) == 0 && cur_offset); pdep->de_flag |= DE_UPDATE; return 0; } /* * Create a unique DOS name in dvp */ __private_extern__ int uniqdosname( struct denode *dep, struct componentname *cnp, u_char *cp, u_int8_t *lower_case, vfs_context_t context) { struct msdosfsmount *pmp = dep->de_pmp; struct dosdirentry *dentp; int gen; u_long blsize; u_long cn; daddr64_t bn; struct buf *bp; int error; u_int16_t ucfn[WIN_MAXLEN]; size_t unichars; char *bdata; /* * Decode component name into Unicode */ (void) utf8_decodestr(cnp->cn_nameptr, cnp->cn_namelen, ucfn, &unichars, sizeof(ucfn), 0, UTF_PRECOMPOSED); unichars /= 2; /* bytes to chars */ for (gen = 1;; gen++) { /* * Generate DOS name with generation number */ if (!unicode2dosfn(ucfn, cp, unichars, gen, lower_case)) return gen == 1 ? ENAMETOOLONG : EEXIST; /* This function calls unicode2dosfn to get short name for given * file name and does not use long names any further. Therefore * we do not call mac2sfmfn to convert Mac Unicode to SFM Unicode */ /* * Now look for a dir entry with this exact name */ for (cn = error = 0; !error; cn++) { if ((error = pcbmap(dep, cn, 1, &bn, NULL, &blsize, context)) != 0) { if (error == E2BIG) /* EOF reached and not found */ return 0; return error; } error = (int)buf_meta_bread(pmp->pm_devvp, bn, blsize, vfs_context_ucred(context), &bp); if (error) { buf_brelse(bp); return error; } bdata = (char *)buf_dataptr(bp); for (dentp = (struct dosdirentry *)bdata; (char *)dentp < bdata + blsize; dentp++) { if (dentp->deName[0] == SLOT_EMPTY) { /* * Last used entry and not found */ buf_brelse(bp); return 0; } /* * Ignore volume labels and Win95 entries */ if (dentp->deAttributes & ATTR_VOLUME) continue; if (!bcmp(dentp->deName, cp, 11)) { error = EEXIST; break; } } buf_brelse(bp); } } } /* * Find room in a directory to create the entries for a given name. * * Inputs: * dep directory to search for free/unused entries * cnp the name to be created (used to determine number of slots needed). * * Outputs: * lower_case The case ("NT") flags for the new name * offset Byte offset from start of directory where short name enty goes * long_count Number of entries needed for long name entries */ __private_extern__ int findslots( struct denode *dep, struct componentname *cnp, u_int8_t *lower_case, u_long *offset, u_long *long_count, vfs_context_t context) { int error; u_char dosfilename[12]; u_int16_t ucfn[WIN_MAXLEN]; size_t unichars; int wincnt=0; /* Number of consecutive entries needed for long name + dir entry */ int slotcount; /* Number of consecutive entries found so far */ u_long diroff; /* Byte offset of entry from start of directory */ int blkoff; /* Byte offset of entry from start of block */ int frcn; /* File (directory) relative cluster number */ daddr64_t bn; /* Physical disk block number */ u_long cluster; /* Physical cluster */ u_long blsize; /* Size of directory cluster, in bytes */ struct dosdirentry *entry; struct msdosfsmount *pmp; struct buf *bp; char *bdata; pmp = dep->de_pmp; /* * Decode name into UCS-2 (Unicode) */ (void) utf8_decodestr(cnp->cn_nameptr, cnp->cn_namelen, ucfn, &unichars, sizeof(ucfn), 0, UTF_PRECOMPOSED); unichars /= 2; /* bytes to chars */ /* * Determine the number of consecutive directory entries we'll need. */ switch (unicode2dosfn(ucfn, dosfilename, unichars, 0, lower_case)) { case 0: /* * The name is syntactically invalid. Normally, we'd return EINVAL, * but ENAMETOOLONG makes it clear that the name is the problem (and * allows Carbon to return a more meaningful error). */ return (ENAMETOOLONG); case 1: /* * The name is already a short, DOS name, so no long name entries needed. */ wincnt = 1; break; case 2: case 3: wincnt = winSlotCnt(ucfn, unichars) + 1; break; } /* This function calls unicode2dosfn to get short name for given * file name and does not use long names any further. Therefore * we do not call mac2sfmfn to convert Mac Unicode to SFM Unicode */ /* * Look for some consecutive unused directory entries. */ slotcount = 0; /* None found yet. */ /* The outer loop ranges over the clusters in the directory. */ diroff = 0; for (frcn = 0; ; frcn++) { error = pcbmap(dep, frcn, 1, &bn, &cluster, &blsize, context); if (error) { if (error == E2BIG) break; return (error); } error = (int)buf_meta_bread(pmp->pm_devvp, bn, blsize, vfs_context_ucred(context), &bp); if (error) { buf_brelse(bp); return (error); } bdata = (char *)buf_dataptr(bp); /* Loop over entries in the cluster. */ for (blkoff = 0; blkoff < blsize; blkoff += sizeof(struct dosdirentry), diroff += sizeof(struct dosdirentry)) { entry = (struct dosdirentry *)(bdata + blkoff); if (entry->deName[0] == SLOT_EMPTY || entry->deName[0] == SLOT_DELETED) { slotcount++; if (slotcount == wincnt) { /* Found enough space! */ *offset = diroff; goto found; } } else { /* Empty space wasn't big enough, so forget about it. */ slotcount = 0; } } buf_brelse(bp); bp = NULL; } /* * Fix up the slot description to point to where we would put the * DOS entry (with Win95 long name entries before that). If we * would need to grow the directory, then the offset will be greater * than or equal to the size of the directory. */ found: if (wincnt > slotcount) { /* * If we get here, we hit the end of the directory without finding * enough consecutive slots. "slotcount" is the number of free slots * at the end of the last cluster. "diroff" is the size of the * directory, in bytes. * * Note the "- 1" below; that's because the returned offset is the * offset of the last slot that would be used (for the short name * entry); without subtracting one, we'd end up pointing to the slot * immediately past the last one being used. */ *offset = diroff + sizeof(struct dosdirentry) * (wincnt - slotcount - 1); } *long_count = wincnt - 1; if (bp) buf_brelse(bp); return 0; }