/* * Create a unique DOS name in dvp */ int uniqdosname(struct denode *dep, struct componentname *cnp, u_char *cp) { struct msdosfsmount *pmp = dep->de_pmp; struct direntry *dentp; int gen; int blsize; u_long cn; daddr_t bn; struct buf *bp; int error; for (gen = 1;; gen++) { /* * Generate DOS name with generation number */ if (!unix2dosfn((const u_char *)cnp->cn_nameptr, cp, cnp->cn_namelen, gen)) return gen == 1 ? EINVAL : EEXIST; /* * Now look for a dir entry with this exact name */ for (cn = error = 0; !error; cn++) { if ((error = pcbmap(dep, cn, &bn, 0, &blsize)) != 0) { if (error == E2BIG) /* EOF reached and not found */ return 0; return error; } error = bread(pmp->pm_devvp, de_bn2kb(pmp, bn), blsize, NOCRED, 0, &bp); if (error) { return error; } for (dentp = (struct direntry *)bp->b_data; (char *)dentp < (char *)bp->b_data + blsize; dentp++) { if (dentp->deName[0] == SLOT_EMPTY) { /* * Last used entry and not found */ brelse(bp, 0); return 0; } /* * Ignore volume labels and Win95 entries */ if (dentp->deAttributes & ATTR_VOLUME) continue; if (!memcmp(dentp->deName, cp, 11)) { error = EEXIST; break; } } brelse(bp, 0); } } }
/* * Find any Win'95 long filename entry in directory dep */ int findwin95(struct denode *dep) { struct msdosfsmount *pmp = dep->de_pmp; struct direntry *dentp; int blsize, win95; u_long cn; daddr_t bn; struct buf *bp; win95 = 1; /* * Read through the directory looking for Win'95 entries * XXX Note: Error currently handled just as EOF */ for (cn = 0;; cn++) { if (pcbmap(dep, cn, &bn, 0, &blsize)) return win95; if (bread(pmp->pm_devvp, de_bn2kb(pmp, bn), blsize, NOCRED, 0, &bp)) { return win95; } for (dentp = (struct direntry *)bp->b_data; (char *)dentp < (char *)bp->b_data + blsize; dentp++) { if (dentp->deName[0] == SLOT_EMPTY) { /* * Last used entry and not found */ brelse(bp, 0); return win95; } if (dentp->deName[0] == SLOT_DELETED) { /* * Ignore deleted files * Note: might be an indication of Win'95 * anyway XXX */ continue; } if (dentp->deAttributes == ATTR_WIN95) { brelse(bp, 0); return 1; } win95 = 0; } brelse(bp, 0); } }
/* * 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. */ int readep(struct msdosfsmount *pmp, u_long dirclust, u_long diroffset, struct buf **bpp, struct direntry **epp) { int error; daddr_t bn; int blsize; 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 = bread(pmp->pm_devvp, de_bn2kb(pmp, bn), blsize, NOCRED, 0, bpp)) != 0) { *bpp = NULL; return (error); } if (epp) *epp = bptoep(pmp, *bpp, diroffset); return (0); }
/* * 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. */ int removede(struct denode *pdep, struct denode *dep) /* pdep: directory where the entry is removed */ /* dep: file to be removed */ { int error; struct direntry *ep; struct buf *bp; daddr_t bn; int blsize; struct msdosfsmount *pmp = pdep->de_pmp; u_long offset = pdep->de_fndoffset; #ifdef _KERNEL int async = pdep->de_pmp->pm_mountp->mnt_flag & MNT_ASYNC; #else #define async 0 #endif #ifdef MSDOSFS_DEBUG printf("removede(): filename %s, dep %p, offset %08lx\n", dep->de_Name, dep, offset); #endif dep->de_refcnt--; offset += sizeof(struct direntry); do { offset -= sizeof(struct direntry); error = pcbmap(pdep, de_cluster(pmp, offset), &bn, 0, &blsize); if (error) return error; error = bread(pmp->pm_devvp, de_bn2kb(pmp, bn), blsize, NOCRED, B_MODIFY, &bp); if (error) { return error; } ep = bptoep(pmp, bp, offset); /* * Check whether, if we came here the second time, i.e. * when underflowing into the previous block, the last * entry in this block is a longfilename entry, too. */ if (ep->deAttributes != ATTR_WIN95 && offset != pdep->de_fndoffset) { brelse(bp, 0); break; } offset += sizeof(struct direntry); 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. */ offset -= sizeof(struct direntry); ep--->deName[0] = SLOT_DELETED; if ((pmp->pm_flags & MSDOSFSMNT_NOWIN95) || !(offset & pmp->pm_crbomask) || ep->deAttributes != ATTR_WIN95) break; } if (async) bdwrite(bp); else if ((error = bwrite(bp)) != 0) return error; } while (!(pmp->pm_flags & MSDOSFSMNT_NOWIN95) && !(offset & pmp->pm_crbomask) && offset); return 0; }
/* * 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. bread/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. */ int msdosfs_lookup(void *v) { struct vop_lookup_v2_args /* { struct vnode *a_dvp; struct vnode **a_vpp; struct componentname *a_cnp; } */ *ap = v; struct vnode *vdp = ap->a_dvp; struct vnode **vpp = ap->a_vpp; struct componentname *cnp = ap->a_cnp; daddr_t bn; int error; int slotcount; int slotoffset = 0; int frcn; u_long cluster; int blkoff; int diroff; int blsize; int isadir; /* ~0 if found direntry is a directory */ u_long scn; /* starting cluster number */ struct vnode *pdp; struct denode *dp; struct denode *tdp; struct msdosfsmount *pmp; struct buf *bp = 0; struct direntry *dep; u_char dosfilename[12]; int flags; int nameiop = cnp->cn_nameiop; int wincnt = 1; int chksum = -1, chksum_ok; int olddos = 1; flags = cnp->cn_flags; #ifdef MSDOSFS_DEBUG printf("msdosfs_lookup(): looking for %.*s\n", (int)cnp->cn_namelen, cnp->cn_nameptr); #endif dp = VTODE(vdp); pmp = dp->de_pmp; *vpp = NULL; #ifdef MSDOSFS_DEBUG printf("msdosfs_lookup(): vdp %p, dp %p, Attr %02x\n", vdp, dp, dp->de_Attributes); #endif /* * Check accessiblity of directory. */ if ((error = VOP_ACCESS(vdp, VEXEC, cnp->cn_cred)) != 0) return (error); if ((flags & ISLASTCN) && (vdp->v_mount->mnt_flag & MNT_RDONLY) && (cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME)) return (EROFS); /* * We now have a segment name to search for, and a directory to search. * * Before tediously performing a linear scan of the directory, * check the name cache to see if the directory/name pair * we are looking for is known already. */ if (cache_lookup(vdp, cnp->cn_nameptr, cnp->cn_namelen, cnp->cn_nameiop, cnp->cn_flags, NULL, vpp)) { return *vpp == NULLVP ? ENOENT: 0; } /* * 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 ((vdp->v_vflag & VV_ROOT) && cnp->cn_nameptr[0] == '.' && (cnp->cn_namelen == 1 || (cnp->cn_namelen == 2 && cnp->cn_nameptr[1] == '.'))) { isadir = ATTR_DIRECTORY; scn = MSDOSFSROOT; #ifdef MSDOSFS_DEBUG printf("msdosfs_lookup(): looking for . or .. in root directory\n"); #endif cluster = MSDOSFSROOT; blkoff = MSDOSFSROOT_OFS; goto foundroot; } switch (unix2dosfn((const u_char *)cnp->cn_nameptr, dosfilename, cnp->cn_namelen, 0)) { case 0: return (EINVAL); case 1: break; case 2: wincnt = winSlotCnt((const u_char *)cnp->cn_nameptr, cnp->cn_namelen) + 1; break; case 3: olddos = 0; wincnt = winSlotCnt((const u_char *)cnp->cn_nameptr, cnp->cn_namelen) + 1; break; } if (pmp->pm_flags & MSDOSFSMNT_SHORTNAME) wincnt = 1; /* * Suppress search for slots unless creating * file and at end of pathname, in which case * we watch for a place to put the new file in * case it doesn't already exist. */ slotcount = wincnt; if ((nameiop == CREATE || nameiop == RENAME) && (flags & ISLASTCN)) slotcount = 0; #ifdef MSDOSFS_DEBUG printf("msdosfs_lookup(): dos filename: %s\n", dosfilename); #endif /* * Search the directory pointed at by vdp for the name pointed at * by cnp->cn_nameptr. */ tdp = 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; diroff < dp->de_FileSize; frcn++) { if ((error = pcbmap(dp, frcn, &bn, &cluster, &blsize)) != 0) { if (error == E2BIG) break; return (error); } error = bread(pmp->pm_devvp, de_bn2kb(pmp, bn), blsize, NOCRED, 0, &bp); if (error) { return (error); } for (blkoff = 0; blkoff < blsize; blkoff += sizeof(struct direntry), diroff += sizeof(struct direntry)) { dep = (struct direntry *)((char *)bp->b_data + 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 (slotcount < wincnt) { slotcount++; slotoffset = diroff; } if (dep->deName[0] == SLOT_EMPTY) { brelse(bp, 0); goto notfound; } } else { /* * If there wasn't enough space for our * winentries, forget about the empty space */ if (slotcount < wincnt) slotcount = 0; /* * Check for Win95 long filename entry */ if (dep->deAttributes == ATTR_WIN95) { if (pmp->pm_flags & MSDOSFSMNT_SHORTNAME) continue; chksum = winChkName((const u_char *)cnp->cn_nameptr, cnp->cn_namelen, (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 */ chksum_ok = (chksum == winChksum(dep->deName)); if (!chksum_ok && ( !olddos || memcmp(&dosfilename[0],dep->deName,8) || memcmp(&dosfilename[8],dep->deExtension,3))) { chksum = -1; continue; } #ifdef MSDOSFS_DEBUG printf("msdosfs_lookup(): match blkoff %d, diroff %d\n", blkoff, diroff); #endif /* * Remember where this directory * entry came from for whoever did * this lookup. */ dp->de_fndoffset = diroff; if (chksum_ok && nameiop == RENAME) { /* * Target had correct long name * directory entries, reuse them * as needed. */ dp->de_fndcnt = wincnt - 1; } else { /* * Long name directory entries * not present or corrupt, can only * reuse dos directory entry. */ dp->de_fndcnt = 0; } goto found; } } /* for (blkoff = 0; .... */ /* * Release the buffer holding the directory cluster just * searched. */ brelse(bp, 0); } /* 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. */ #ifdef MSDOSFS_DEBUG printf("msdosfs_lookup(): op %d, refcnt %ld, slotcount %d, slotoffset %d\n", nameiop, dp->de_refcnt, slotcount, slotoffset); #endif if ((nameiop == CREATE || nameiop == RENAME) && (flags & ISLASTCN) && dp->de_refcnt != 0) { /* * Access for write is interpreted as allowing * creation of files in the directory. */ error = VOP_ACCESS(vdp, VWRITE, cnp->cn_cred); if (error) return (error); /* * Fixup the slot description to point to the place where * we might put the new DOS direntry (putting the Win95 * long name entries before that) */ if (!slotcount) { slotcount = 1; slotoffset = diroff; } if (wincnt > slotcount) { slotoffset += sizeof(struct direntry) * (wincnt - slotcount); } /* * Return an indication of where the new directory * entry should be put. */ dp->de_fndoffset = slotoffset; dp->de_fndcnt = wincnt - 1; /* * We return with the directory locked, so that * the parameters we set up above will still be * valid if we actually decide to do a direnter(). * We return ni_vp == NULL to indicate that the entry * does not currently exist; we leave a pointer to * the (locked) directory inode in ndp->ni_dvp. * * NB - if the directory is unlocked, then this * information cannot be used. */ return (EJUSTRETURN); } #if 0 /* * Insert name into cache (as non-existent) if appropriate. * * XXX Negative caching is broken for msdosfs because the name * cache doesn't understand peculiarities such as case insensitivity * and 8.3 filenames. Hence, it may not invalidate all negative * entries if a file with this name is later created. * e.g. creating a file 'foo' won't invalidate a negative entry * for 'FOO'. */ if (nameiop != CREATE) cache_enter(vdp, *vpp, cnp->cn_nameptr, cnp->cn_namelen, cnp->cn_flags); #endif return (ENOENT); 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) { cluster = scn; if (cluster == MSDOSFSROOT) blkoff = MSDOSFSROOT_OFS; else blkoff = 0; } else if (cluster == MSDOSFSROOT) blkoff = diroff; /* * Now release buf to allow deget to read the entry again. * Reserving it here and giving it to deget could result * in a deadlock. */ brelse(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. * Lock the inode, being careful with ".". */ if (nameiop == DELETE && (flags & ISLASTCN)) { /* * Don't allow deleting the root. */ if (blkoff == MSDOSFSROOT_OFS) return EINVAL; /* * Write access to directory required to delete files. */ error = VOP_ACCESS(vdp, VWRITE, cnp->cn_cred); if (error) return (error); /* * Return pointer to current entry in dp->i_offset. * Save directory inode pointer in ndp->ni_dvp for dirremove(). */ if (dp->de_StartCluster == scn && isadir) { /* "." */ vref(vdp); *vpp = vdp; return (0); } if ((error = deget(pmp, cluster, blkoff, &tdp)) != 0) return (error); *vpp = DETOV(tdp); VOP_UNLOCK(*vpp); return (0); } /* * 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 (vdp->v_mount->mnt_flag & MNT_RDONLY) return (EROFS); if (blkoff == MSDOSFSROOT_OFS) return EINVAL; error = VOP_ACCESS(vdp, VWRITE, cnp->cn_cred); if (error) return (error); /* * Careful about locking second inode. * This can only occur if the target is ".". */ if (dp->de_StartCluster == scn && isadir) return (EISDIR); if ((error = deget(pmp, cluster, blkoff, &tdp)) != 0) return (error); *vpp = DETOV(tdp); VOP_UNLOCK(*vpp); return (0); } /* * Step through the translation in the name. We do not `vput' the * directory because we may need it again if a symbolic link * is relative to the current directory. Instead we save it * unlocked as "pdp". We must get the target inode before unlocking * the directory to insure that the inode will not be removed * before we get it. We prevent deadlock by always fetching * inodes from the root, moving down the directory tree. Thus * when following backward pointers ".." we must unlock the * parent directory before getting the requested directory. * There is a potential race condition here if both the current * and parent directories are removed before the VFS_VGET for the * inode associated with ".." returns. We hope that this occurs * infrequently since we cannot avoid this race condition without * implementing a sophisticated deadlock detection algorithm. * Note also that this simple deadlock detection scheme will not * work if the file system has any hard links other than ".." * that point backwards in the directory structure. */ pdp = vdp; if (flags & ISDOTDOT) { VOP_UNLOCK(pdp); /* race to get the inode */ error = deget(pmp, cluster, blkoff, &tdp); vn_lock(pdp, LK_EXCLUSIVE | LK_RETRY); if (error) { return error; } *vpp = DETOV(tdp); } else if (dp->de_StartCluster == scn && isadir) { vref(vdp); /* we want ourself, ie "." */ *vpp = vdp; } else { if ((error = deget(pmp, cluster, blkoff, &tdp)) != 0) return (error); *vpp = DETOV(tdp); } /* * Insert name into cache if appropriate. */ cache_enter(vdp, *vpp, cnp->cn_nameptr, cnp->cn_namelen, cnp->cn_flags); if (*vpp != vdp) VOP_UNLOCK(*vpp); return 0; }
/* * 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. */ int doscheckpath(struct denode *source, struct denode *target) { u_long scn; struct msdosfsmount *pmp; struct direntry *ep; struct denode *dep; struct buf *bp = NULL; int error = 0; dep = target; if ((target->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; #ifdef DIAGNOSTIC if (pmp != source->de_pmp) panic("doscheckpath: source and target on different filesystems"); #endif 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 = bread(pmp->pm_devvp, de_bn2kb(pmp, cntobn(pmp, scn)), pmp->pm_bpcluster, NOCRED, 0, &bp); if (error) break; ep = (struct direntry *) bp->b_data + 1; if ((ep->deAttributes & ATTR_DIRECTORY) == 0 || memcmp(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; } vput(DETOV(dep)); brelse(bp, 0); bp = NULL; /* NOTE: deget() clears dep on error */ if ((error = deget(pmp, scn, 0, &dep)) != 0) break; } out: if (bp) brelse(bp, 0); if (error == ENOTDIR) printf("doscheckpath(): .. not a directory?\n"); if (dep != NULL) vput(DETOV(dep)); return (error); }
/* * Be sure a directory is empty except for "." and "..". Return 1 if empty, * return 0 if not empty or error. */ int dosdirempty(struct denode *dep) { int blsize; int error; u_long cn; daddr_t bn; struct buf *bp; struct msdosfsmount *pmp = dep->de_pmp; struct direntry *dentp; /* * 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, &bn, 0, &blsize)) != 0) { if (error == E2BIG) return (1); /* it's empty */ return (0); } error = bread(pmp->pm_devvp, de_bn2kb(pmp, bn), blsize, NOCRED, 0, &bp); if (error) { return (0); } for (dentp = (struct direntry *)bp->b_data; (char *)dentp < (char *)bp->b_data + 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) { brelse(bp, 0); return (1); } /* * Any names other than "." and ".." in a * directory mean it is not empty. */ if (memcmp(dentp->deName, ". ", 11) && memcmp(dentp->deName, ".. ", 11)) { brelse(bp, 0); #ifdef MSDOSFS_DEBUG printf("dosdirempty(): found %.11s, %d, %d\n", dentp->deName, dentp->deName[0], dentp->deName[1]); #endif return (0); /* not empty */ } } } brelse(bp, 0); } /* NOTREACHED */ }
/* * 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 */ int createde(struct denode *dep, struct denode *ddep, struct denode **depp, struct componentname *cnp) { int error, rberror; u_long dirclust, clusoffset; u_long fndoffset, havecnt = 0, wcnt = 1, i; struct direntry *ndep; struct msdosfsmount *pmp = ddep->de_pmp; struct buf *bp; daddr_t bn; int blsize; #ifdef _KERNEL int async = ddep->de_pmp->pm_mountp->mnt_flag & MNT_ASYNC; #else #define async 0 #endif #ifdef MSDOSFS_DEBUG printf("createde(dep %p, ddep %p, depp %p, cnp %p)\n", dep, ddep, depp, cnp); #endif /* * 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 (ddep->de_fndoffset >= ddep->de_FileSize) { u_long needlen = ddep->de_fndoffset + sizeof(struct direntry) - ddep->de_FileSize; dirclust = de_clcount(pmp, needlen); if ((error = extendfile(ddep, dirclust, 0, 0, DE_CLEAR)) != 0) { (void)detrunc(ddep, ddep->de_FileSize, 0, NOCRED); goto err_norollback; } /* * 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, ddep->de_fndoffset), &bn, &dirclust, &blsize); if (error) goto err_norollback; clusoffset = ddep->de_fndoffset; if (dirclust != MSDOSFSROOT) clusoffset &= pmp->pm_crbomask; if ((error = bread(pmp->pm_devvp, de_bn2kb(pmp, bn), blsize, NOCRED, B_MODIFY, &bp)) != 0) { goto err_norollback; } ndep = bptoep(pmp, bp, clusoffset); DE_EXTERNALIZE(ndep, dep); /* * Now write the Win95 long name */ if (ddep->de_fndcnt > 0) { u_int8_t chksum = winChksum(ndep->deName); const u_char *un = (const u_char *)cnp->cn_nameptr; int unlen = cnp->cn_namelen; u_long xhavecnt; fndoffset = ddep->de_fndoffset; xhavecnt = ddep->de_fndcnt + 1; for(; wcnt < xhavecnt; wcnt++) { if ((fndoffset & pmp->pm_crbomask) == 0) { /* we should never get here if ddep is root * directory */ if (async) (void) bdwrite(bp); else if ((error = bwrite(bp)) != 0) goto rollback; fndoffset -= sizeof(struct direntry); error = pcbmap(ddep, de_cluster(pmp, fndoffset), &bn, 0, &blsize); if (error) goto rollback; error = bread(pmp->pm_devvp, de_bn2kb(pmp, bn), blsize, NOCRED, B_MODIFY, &bp); if (error) { goto rollback; } ndep = bptoep(pmp, bp, fndoffset & pmp->pm_crbomask); } else { ndep--; fndoffset -= sizeof(struct direntry); } if (!unix2winfn(un, unlen, (struct winentry *)ndep, wcnt, chksum)) break; } } if (async) bdwrite(bp); else if ((error = bwrite(bp)) != 0) goto rollback; /* * If they want us to return with the denode gotten. */ if (depp) { u_long diroffset = clusoffset; 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; } error = deget(pmp, dirclust, diroffset, depp); #ifndef MAKEFS if (error == 0) VOP_UNLOCK(DETOV(*depp)); #endif return error; } return 0; rollback: /* * Mark all slots modified so far as deleted. Note that we * can't just call removede(), since directory is not in * consistent state. */ fndoffset = ddep->de_fndoffset; rberror = pcbmap(ddep, de_cluster(pmp, fndoffset), &bn, NULL, &blsize); if (rberror) goto err_norollback; if ((rberror = bread(pmp->pm_devvp, de_bn2kb(pmp, bn), blsize, NOCRED, B_MODIFY, &bp)) != 0) { goto err_norollback; } ndep = bptoep(pmp, bp, clusoffset); havecnt = ddep->de_fndcnt + 1; for(i = wcnt; i <= havecnt; i++) { /* mark entry as deleted */ ndep->deName[0] = SLOT_DELETED; if ((fndoffset & pmp->pm_crbomask) == 0) { /* we should never get here if ddep is root * directory */ if (async) bdwrite(bp); else if ((rberror = bwrite(bp)) != 0) goto err_norollback; fndoffset -= sizeof(struct direntry); rberror = pcbmap(ddep, de_cluster(pmp, fndoffset), &bn, 0, &blsize); if (rberror) goto err_norollback; rberror = bread(pmp->pm_devvp, de_bn2kb(pmp, bn), blsize, NOCRED, B_MODIFY, &bp); if (rberror) { goto err_norollback; } ndep = bptoep(pmp, bp, fndoffset); } else { ndep--; fndoffset -= sizeof(struct direntry); } } /* ignore any further error */ if (async) (void) bdwrite(bp); else (void) bwrite(bp); err_norollback: return error; }
struct denode * msdosfs_mkdire(const char *path, struct denode *pdep, fsnode *node) { struct denode ndirent; struct denode *dep; struct componentname cn; struct stat *st = &node->inode->st; struct msdosfsmount *pmp = pdep->de_pmp; int error; u_long newcluster, pcl, bn; daddr_t lbn; struct direntry *denp; struct buf *bp; cn.cn_nameptr = node->name; cn.cn_namelen = strlen(node->name); /* * If this is the root directory and there is no space left we * can't do anything. This is because the root directory can not * change size. */ if (pdep->de_StartCluster == MSDOSFSROOT && pdep->de_fndoffset >= pdep->de_FileSize) { error = ENOSPC; goto bad2; } /* * Allocate a cluster to hold the about to be created directory. */ error = clusteralloc(pmp, 0, 1, &newcluster, NULL); if (error) goto bad2; memset(&ndirent, 0, sizeof(ndirent)); ndirent.de_pmp = pmp; ndirent.de_flag = DE_ACCESS | DE_CREATE | DE_UPDATE; msdosfs_times(pmp, &ndirent, st); /* * Now fill the cluster with the "." and ".." entries. And write * the cluster to disk. This way it is there for the parent * directory to be pointing at if there were a crash. */ bn = cntobn(pmp, newcluster); lbn = de_bn2kb(pmp, bn); DPRINTF(("%s(newcluster %lu, bn=%lu, lbn=%lu)\n", __func__, newcluster, bn, lbn)); /* always succeeds */ bp = getblk(pmp->pm_devvp, lbn, pmp->pm_bpcluster, 0, 0); memset(bp->b_data, 0, pmp->pm_bpcluster); memcpy(bp->b_data, &dosdirtemplate, sizeof dosdirtemplate); denp = (struct direntry *)bp->b_data; putushort(denp[0].deStartCluster, newcluster); putushort(denp[0].deCDate, ndirent.de_CDate); putushort(denp[0].deCTime, ndirent.de_CTime); denp[0].deCHundredth = ndirent.de_CHun; putushort(denp[0].deADate, ndirent.de_ADate); putushort(denp[0].deMDate, ndirent.de_MDate); putushort(denp[0].deMTime, ndirent.de_MTime); pcl = pdep->de_StartCluster; DPRINTF(("%s(pcl %lu, rootdirblk=%lu)\n", __func__, pcl, pmp->pm_rootdirblk)); if (FAT32(pmp) && pcl == pmp->pm_rootdirblk) pcl = 0; putushort(denp[1].deStartCluster, pcl); putushort(denp[1].deCDate, ndirent.de_CDate); putushort(denp[1].deCTime, ndirent.de_CTime); denp[1].deCHundredth = ndirent.de_CHun; putushort(denp[1].deADate, ndirent.de_ADate); putushort(denp[1].deMDate, ndirent.de_MDate); putushort(denp[1].deMTime, ndirent.de_MTime); if (FAT32(pmp)) { putushort(denp[0].deHighClust, newcluster >> 16); putushort(denp[1].deHighClust, pdep->de_StartCluster >> 16); } else {
/* * Write data to a file or directory. */ static int msdosfs_wfile(const char *path, struct denode *dep, fsnode *node) { int error, fd; size_t osize = dep->de_FileSize; struct stat *st = &node->inode->st; size_t nsize, offs; struct msdosfsmount *pmp = dep->de_pmp; struct buf *bp; char *dat; u_long cn = 0; error = 0; /* XXX: gcc/vax */ DPRINTF(("%s(diroff %lu, dirclust %lu, startcluster %lu)\n", __func__, dep->de_diroffset, dep->de_dirclust, dep->de_StartCluster)); if (st->st_size == 0) return 0; /* Don't bother to try to write files larger than the fs limit */ if (st->st_size > MSDOSFS_FILESIZE_MAX) { errno = EFBIG; return -1; } nsize = st->st_size; DPRINTF(("%s(nsize=%zu, osize=%zu)\n", __func__, nsize, osize)); if (nsize > osize) { if ((error = deextend(dep, nsize, NULL)) != 0) { errno = error; return -1; } if ((error = msdosfs_updatede(dep)) != 0) { errno = error; return -1; } } if ((fd = open(path, O_RDONLY)) == -1) err(1, "open %s", path); if ((dat = mmap(0, nsize, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, 0)) == MAP_FAILED) { DPRINTF(("%s: mmap %s %s", __func__, node->name, strerror(errno))); close(fd); goto out; } close(fd); for (offs = 0; offs < nsize;) { int blsize, cpsize; daddr_t bn; u_long on = offs & pmp->pm_crbomask; #ifdef HACK cn = dep->de_StartCluster; if (cn == MSDOSFSROOT) { DPRINTF(("%s: bad lbn %lu", __func__, cn)); goto out; } bn = cntobn(pmp, cn); blsize = pmp->pm_bpcluster; #else if ((error = pcbmap(dep, cn++, &bn, NULL, &blsize)) != 0) { DPRINTF(("%s: pcbmap %lu", __func__, bn)); goto out; } #endif DPRINTF(("%s(cn=%lu, bn=%llu/%llu, blsize=%d)\n", __func__, cn, (unsigned long long)bn, (unsigned long long)de_bn2kb(pmp, bn), blsize)); if ((error = bread(pmp->pm_devvp, de_bn2kb(pmp, bn), blsize, 0, &bp)) != 0) { DPRINTF(("bread %d\n", error)); goto out; } cpsize = MIN((nsize - offs), blsize - on); memcpy((char *)bp->b_data + on, dat + offs, cpsize); bwrite(bp); offs += cpsize; } munmap(dat, nsize); return 0; out: munmap(dat, nsize); return error; }
/* * 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. bread/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. */ static int msdosfs_findslot(struct denode *dp, struct componentname *cnp) { daddr_t bn; int error; int slotcount; int slotoffset = 0; int frcn; u_long cluster; int blkoff; u_int diroff; int blsize; struct msdosfsmount *pmp; struct buf *bp = 0; struct direntry *dep; u_char dosfilename[12]; int wincnt = 1; int chksum = -1, chksum_ok; int olddos = 1; pmp = dp->de_pmp; switch (unix2dosfn((const u_char *)cnp->cn_nameptr, dosfilename, cnp->cn_namelen, 0)) { case 0: return (EINVAL); case 1: break; case 2: wincnt = winSlotCnt((const u_char *)cnp->cn_nameptr, cnp->cn_namelen, pmp->pm_flags & MSDOSFSMNT_UTF8) + 1; break; case 3: olddos = 0; wincnt = winSlotCnt((const u_char *)cnp->cn_nameptr, cnp->cn_namelen, pmp->pm_flags & MSDOSFSMNT_UTF8) + 1; break; } if (pmp->pm_flags & MSDOSFSMNT_SHORTNAME) wincnt = 1; /* * Suppress search for slots unless creating * file and at end of pathname, in which case * we watch for a place to put the new file in * case it doesn't already exist. */ slotcount = 0; DPRINTF(("%s(): dos filename: %s\n", __func__, dosfilename)); /* * Search the directory pointed at by vdp for the name pointed at * by cnp->cn_nameptr. */ /* * 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; diroff < dp->de_FileSize; frcn++) { if ((error = pcbmap(dp, frcn, &bn, &cluster, &blsize)) != 0) { if (error == E2BIG) break; return (error); } error = bread(pmp->pm_devvp, de_bn2kb(pmp, bn), blsize, 0, &bp); if (error) { return (error); } for (blkoff = 0; blkoff < blsize; blkoff += sizeof(struct direntry), diroff += sizeof(struct direntry)) { dep = (struct direntry *)((char *)bp->b_data + 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 (slotcount < wincnt) { slotcount++; slotoffset = diroff; } if (dep->deName[0] == SLOT_EMPTY) { brelse(bp, 0); goto notfound; } } else { /* * If there wasn't enough space for our * winentries, forget about the empty space */ if (slotcount < wincnt) slotcount = 0; /* * Check for Win95 long filename entry */ if (dep->deAttributes == ATTR_WIN95) { if (pmp->pm_flags & MSDOSFSMNT_SHORTNAME) continue; chksum = winChkName((const u_char *)cnp->cn_nameptr, cnp->cn_namelen, (struct winentry *)dep, chksum, pmp->pm_flags & MSDOSFSMNT_UTF8); 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 */ chksum_ok = (chksum == winChksum(dep->deName)); if (!chksum_ok && (!olddos || memcmp(dosfilename, dep->deName, 11))) { chksum = -1; continue; } DPRINTF(("%s(): match blkoff %d, diroff %d\n", __func__, blkoff, diroff)); /* * Remember where this directory * entry came from for whoever did * this lookup. */ dp->de_fndoffset = diroff; dp->de_fndcnt = 0; return EEXIST; } } /* for (blkoff = 0; .... */ /* * Release the buffer holding the directory cluster just * searched. */ brelse(bp, 0); } /* 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. */ DPRINTF(("%s(): refcnt %ld, slotcount %d, slotoffset %d\n", __func__, dp->de_refcnt, slotcount, slotoffset)); /* * Fixup the slot description to point to the place where * we might put the new DOS direntry (putting the Win95 * long name entries before that) */ if (!slotcount) { slotcount = 1; slotoffset = diroff; } if (wincnt > slotcount) { slotoffset += sizeof(struct direntry) * (wincnt - slotcount); } /* * Return an indication of where the new directory * entry should be put. */ dp->de_fndoffset = slotoffset; dp->de_fndcnt = wincnt - 1; /* * We return with the directory locked, so that * the parameters we set up above will still be * valid if we actually decide to do a direnter(). * We return ni_vp == NULL to indicate that the entry * does not currently exist; we leave a pointer to * the (locked) directory inode in ndp->ni_dvp. * * NB - if the directory is unlocked, then this * information cannot be used. */ return 0; }
int msdosfs_mountfs(struct vnode *devvp, struct mount *mp, struct lwp *l, struct msdosfs_args *argp) { struct msdosfsmount *pmp; struct buf *bp; dev_t dev = devvp->v_rdev; union bootsector *bsp; struct byte_bpb33 *b33; struct byte_bpb50 *b50; struct byte_bpb710 *b710; uint8_t SecPerClust; int ronly, error, tmp; int bsize; uint64_t psize; unsigned secsize; /* Flush out any old buffers remaining from a previous use. */ if ((error = vinvalbuf(devvp, V_SAVE, l->l_cred, l, 0, 0)) != 0) return (error); ronly = (mp->mnt_flag & MNT_RDONLY) != 0; bp = NULL; /* both used in error_exit */ pmp = NULL; error = fstrans_mount(mp); if (error) goto error_exit; error = getdisksize(devvp, &psize, &secsize); if (error) { if (argp->flags & MSDOSFSMNT_GEMDOSFS) goto error_exit; /* ok, so it failed. we most likely don't need the info */ secsize = DEV_BSIZE; psize = 0; error = 0; } if (argp->flags & MSDOSFSMNT_GEMDOSFS) { bsize = secsize; if (bsize != 512) { DPRINTF(("Invalid block bsize %d for GEMDOS\n", bsize)); error = EINVAL; goto error_exit; } } else bsize = 0; /* * Read the boot sector of the filesystem, and then check the * boot signature. If not a dos boot sector then error out. */ if ((error = bread(devvp, 0, secsize, NOCRED, 0, &bp)) != 0) goto error_exit; bsp = (union bootsector *)bp->b_data; b33 = (struct byte_bpb33 *)bsp->bs33.bsBPB; b50 = (struct byte_bpb50 *)bsp->bs50.bsBPB; b710 = (struct byte_bpb710 *)bsp->bs710.bsBPB; if (!(argp->flags & MSDOSFSMNT_GEMDOSFS)) { if (bsp->bs50.bsBootSectSig0 != BOOTSIG0 || bsp->bs50.bsBootSectSig1 != BOOTSIG1) { DPRINTF(("bootsig0 %d bootsig1 %d\n", bsp->bs50.bsBootSectSig0, bsp->bs50.bsBootSectSig1)); error = EINVAL; goto error_exit; } } pmp = malloc(sizeof *pmp, M_MSDOSFSMNT, M_WAITOK); memset(pmp, 0, sizeof *pmp); pmp->pm_mountp = mp; /* * Compute several useful quantities from the bpb in the * bootsector. Copy in the dos 5 variant of the bpb then fix up * the fields that are different between dos 5 and dos 3.3. */ SecPerClust = b50->bpbSecPerClust; pmp->pm_BytesPerSec = getushort(b50->bpbBytesPerSec); pmp->pm_ResSectors = getushort(b50->bpbResSectors); pmp->pm_FATs = b50->bpbFATs; pmp->pm_RootDirEnts = getushort(b50->bpbRootDirEnts); pmp->pm_Sectors = getushort(b50->bpbSectors); pmp->pm_FATsecs = getushort(b50->bpbFATsecs); pmp->pm_SecPerTrack = getushort(b50->bpbSecPerTrack); pmp->pm_Heads = getushort(b50->bpbHeads); pmp->pm_Media = b50->bpbMedia; if (!(argp->flags & MSDOSFSMNT_GEMDOSFS)) { /* XXX - We should probably check more values here */ if (!pmp->pm_BytesPerSec || !SecPerClust || pmp->pm_SecPerTrack > 63) { DPRINTF(("bytespersec %d secperclust %d " "secpertrack %d\n", pmp->pm_BytesPerSec, SecPerClust, pmp->pm_SecPerTrack)); error = EINVAL; goto error_exit; } } if (pmp->pm_Sectors == 0) { pmp->pm_HiddenSects = getulong(b50->bpbHiddenSecs); pmp->pm_HugeSectors = getulong(b50->bpbHugeSectors); } else { pmp->pm_HiddenSects = getushort(b33->bpbHiddenSecs); pmp->pm_HugeSectors = pmp->pm_Sectors; } if (pmp->pm_RootDirEnts == 0) { unsigned short vers = getushort(b710->bpbFSVers); /* * Some say that bsBootSectSig[23] must be zero, but * Windows does not require this and some digital cameras * do not set these to zero. Therefore, do not insist. */ if (pmp->pm_Sectors || pmp->pm_FATsecs || vers) { DPRINTF(("sectors %d fatsecs %lu vers %d\n", pmp->pm_Sectors, pmp->pm_FATsecs, vers)); error = EINVAL; goto error_exit; } pmp->pm_fatmask = FAT32_MASK; pmp->pm_fatmult = 4; pmp->pm_fatdiv = 1; pmp->pm_FATsecs = getulong(b710->bpbBigFATsecs); /* mirrorring is enabled if the FATMIRROR bit is not set */ if ((getushort(b710->bpbExtFlags) & FATMIRROR) == 0) pmp->pm_flags |= MSDOSFS_FATMIRROR; else pmp->pm_curfat = getushort(b710->bpbExtFlags) & FATNUM; } else pmp->pm_flags |= MSDOSFS_FATMIRROR; if (argp->flags & MSDOSFSMNT_GEMDOSFS) { if (FAT32(pmp)) { DPRINTF(("FAT32 for GEMDOS\n")); /* * GEMDOS doesn't know FAT32. */ error = EINVAL; goto error_exit; } /* * Check a few values (could do some more): * - logical sector size: power of 2, >= block size * - sectors per cluster: power of 2, >= 1 * - number of sectors: >= 1, <= size of partition */ if ( (SecPerClust == 0) || (SecPerClust & (SecPerClust - 1)) || (pmp->pm_BytesPerSec < bsize) || (pmp->pm_BytesPerSec & (pmp->pm_BytesPerSec - 1)) || (pmp->pm_HugeSectors == 0) || (pmp->pm_HugeSectors * (pmp->pm_BytesPerSec / bsize) > psize)) { DPRINTF(("consistency checks for GEMDOS\n")); error = EINVAL; goto error_exit; } /* * XXX - Many parts of the msdosfs driver seem to assume that * the number of bytes per logical sector (BytesPerSec) will * always be the same as the number of bytes per disk block * Let's pretend it is. */ tmp = pmp->pm_BytesPerSec / bsize; pmp->pm_BytesPerSec = bsize; pmp->pm_HugeSectors *= tmp; pmp->pm_HiddenSects *= tmp; pmp->pm_ResSectors *= tmp; pmp->pm_Sectors *= tmp; pmp->pm_FATsecs *= tmp; SecPerClust *= tmp; } /* Check that fs has nonzero FAT size */ if (pmp->pm_FATsecs == 0) { DPRINTF(("FATsecs is 0\n")); error = EINVAL; goto error_exit; } pmp->pm_fatblk = pmp->pm_ResSectors; if (FAT32(pmp)) { pmp->pm_rootdirblk = getulong(b710->bpbRootClust); pmp->pm_firstcluster = pmp->pm_fatblk + (pmp->pm_FATs * pmp->pm_FATsecs); pmp->pm_fsinfo = getushort(b710->bpbFSInfo); } else { pmp->pm_rootdirblk = pmp->pm_fatblk + (pmp->pm_FATs * pmp->pm_FATsecs); pmp->pm_rootdirsize = (pmp->pm_RootDirEnts * sizeof(struct direntry) + pmp->pm_BytesPerSec - 1) / pmp->pm_BytesPerSec;/* in sectors */ pmp->pm_firstcluster = pmp->pm_rootdirblk + pmp->pm_rootdirsize; } pmp->pm_nmbrofclusters = (pmp->pm_HugeSectors - pmp->pm_firstcluster) / SecPerClust; pmp->pm_maxcluster = pmp->pm_nmbrofclusters + 1; pmp->pm_fatsize = pmp->pm_FATsecs * pmp->pm_BytesPerSec; if (argp->flags & MSDOSFSMNT_GEMDOSFS) { if (pmp->pm_nmbrofclusters <= (0xff0 - 2)) { pmp->pm_fatmask = FAT12_MASK; pmp->pm_fatmult = 3; pmp->pm_fatdiv = 2; } else { pmp->pm_fatmask = FAT16_MASK; pmp->pm_fatmult = 2; pmp->pm_fatdiv = 1; } } else if (pmp->pm_fatmask == 0) { if (pmp->pm_maxcluster <= ((CLUST_RSRVD - CLUST_FIRST) & FAT12_MASK)) { /* * This will usually be a floppy disk. This size makes * sure that one FAT entry will not be split across * multiple blocks. */ pmp->pm_fatmask = FAT12_MASK; pmp->pm_fatmult = 3; pmp->pm_fatdiv = 2; } else { pmp->pm_fatmask = FAT16_MASK; pmp->pm_fatmult = 2; pmp->pm_fatdiv = 1; } } if (FAT12(pmp)) pmp->pm_fatblocksize = 3 * pmp->pm_BytesPerSec; else pmp->pm_fatblocksize = MAXBSIZE; pmp->pm_fatblocksec = pmp->pm_fatblocksize / pmp->pm_BytesPerSec; pmp->pm_bnshift = ffs(pmp->pm_BytesPerSec) - 1; /* * Compute mask and shift value for isolating cluster relative byte * offsets and cluster numbers from a file offset. */ pmp->pm_bpcluster = SecPerClust * pmp->pm_BytesPerSec; pmp->pm_crbomask = pmp->pm_bpcluster - 1; pmp->pm_cnshift = ffs(pmp->pm_bpcluster) - 1; /* * Check for valid cluster size * must be a power of 2 */ if (pmp->pm_bpcluster ^ (1 << pmp->pm_cnshift)) { DPRINTF(("bpcluster %lu cnshift %lu\n", pmp->pm_bpcluster, pmp->pm_cnshift)); error = EINVAL; goto error_exit; } /* * Cluster size must be within limit of MAXBSIZE. * Many FAT filesystems will not have clusters larger than * 32KiB due to limits in Windows versions before Vista. */ if (pmp->pm_bpcluster > MAXBSIZE) { DPRINTF(("bpcluster %lu > MAXBSIZE %d\n", pmp->pm_bpcluster, MAXBSIZE)); error = EINVAL; goto error_exit; } /* * Release the bootsector buffer. */ brelse(bp, BC_AGE); bp = NULL; /* * Check FSInfo. */ if (pmp->pm_fsinfo) { struct fsinfo *fp; /* * XXX If the fsinfo block is stored on media with * 2KB or larger sectors, is the fsinfo structure * padded at the end or in the middle? */ if ((error = bread(devvp, de_bn2kb(pmp, pmp->pm_fsinfo), pmp->pm_BytesPerSec, NOCRED, 0, &bp)) != 0) goto error_exit; fp = (struct fsinfo *)bp->b_data; if (!memcmp(fp->fsisig1, "RRaA", 4) && !memcmp(fp->fsisig2, "rrAa", 4) && !memcmp(fp->fsisig3, "\0\0\125\252", 4) && !memcmp(fp->fsisig4, "\0\0\125\252", 4)) pmp->pm_nxtfree = getulong(fp->fsinxtfree); else pmp->pm_fsinfo = 0; brelse(bp, 0); bp = NULL; } /* * Check and validate (or perhaps invalidate?) the fsinfo structure? * XXX */ if (pmp->pm_fsinfo) { if ((pmp->pm_nxtfree == 0xffffffffUL) || (pmp->pm_nxtfree > pmp->pm_maxcluster)) pmp->pm_fsinfo = 0; } /* * Allocate memory for the bitmap of allocated clusters, and then * fill it in. */ pmp->pm_inusemap = malloc(((pmp->pm_maxcluster + N_INUSEBITS) / N_INUSEBITS) * sizeof(*pmp->pm_inusemap), M_MSDOSFSFAT, M_WAITOK); /* * fillinusemap() needs pm_devvp. */ pmp->pm_dev = dev; pmp->pm_devvp = devvp; /* * Have the inuse map filled in. */ if ((error = fillinusemap(pmp)) != 0) { DPRINTF(("fillinusemap %d\n", error)); goto error_exit; } /* * If they want FAT updates to be synchronous then let them suffer * the performance degradation in exchange for the on disk copy of * the FAT being correct just about all the time. I suppose this * would be a good thing to turn on if the kernel is still flakey. */ if (mp->mnt_flag & MNT_SYNCHRONOUS) pmp->pm_flags |= MSDOSFSMNT_WAITONFAT; /* * Finish up. */ if (ronly) pmp->pm_flags |= MSDOSFSMNT_RONLY; else pmp->pm_fmod = 1; mp->mnt_data = pmp; mp->mnt_stat.f_fsidx.__fsid_val[0] = (long)dev; mp->mnt_stat.f_fsidx.__fsid_val[1] = makefstype(MOUNT_MSDOS); mp->mnt_stat.f_fsid = mp->mnt_stat.f_fsidx.__fsid_val[0]; mp->mnt_stat.f_namemax = MSDOSFS_NAMEMAX(pmp); mp->mnt_flag |= MNT_LOCAL; mp->mnt_dev_bshift = pmp->pm_bnshift; mp->mnt_fs_bshift = pmp->pm_cnshift; /* * If we ever do quotas for DOS filesystems this would be a place * to fill in the info in the msdosfsmount structure. You dolt, * quotas on dos filesystems make no sense because files have no * owners on dos filesystems. of course there is some empty space * in the directory entry where we could put uid's and gid's. */ spec_node_setmountedfs(devvp, mp); return (0); error_exit: fstrans_unmount(mp); if (bp) brelse(bp, BC_AGE); if (pmp) { if (pmp->pm_inusemap) free(pmp->pm_inusemap, M_MSDOSFSFAT); free(pmp, M_MSDOSFSMNT); mp->mnt_data = NULL; } return (error); }
/* * Truncate the file described by dep to the length specified by length. */ int detrunc(struct denode *dep, u_long length, int flags, struct kauth_cred *cred) { int error; int allerror = 0; u_long eofentry; u_long chaintofree = 0; daddr_t bn, lastblock; int boff; int isadir = dep->de_Attributes & ATTR_DIRECTORY; struct buf *bp; struct msdosfsmount *pmp = dep->de_pmp; #ifdef MSDOSFS_DEBUG printf("detrunc(): file %s, length %lu, flags %x\n", dep->de_Name, length, flags); #endif /* * Disallow attempts to truncate the root directory since it is of * fixed size. That's just the way dos filesystems are. We use * the VROOT bit in the vnode because checking for the directory * bit and a startcluster of 0 in the denode is not adequate to * recognize the root directory at this point in a file or * directory's life. */ if (dep->de_vnode != NULL && !FAT32(pmp)) { printf("detrunc(): can't truncate root directory, clust %ld, offset %ld\n", dep->de_dirclust, dep->de_diroffset); return (EINVAL); } if (dep->de_FileSize < length) return (deextend(dep, length, cred)); lastblock = de_clcount(pmp, length) - 1; /* * If the desired length is 0 then remember the starting cluster of * the file and set the StartCluster field in the directory entry * to 0. If the desired length is not zero, then get the number of * the last cluster in the shortened file. Then get the number of * the first cluster in the part of the file that is to be freed. * Then set the next cluster pointer in the last cluster of the * file to CLUST_EOFE. */ if (length == 0) { chaintofree = dep->de_StartCluster; dep->de_StartCluster = 0; eofentry = ~0; } else { error = pcbmap(dep, lastblock, 0, &eofentry, 0); if (error) { #ifdef MSDOSFS_DEBUG printf("detrunc(): pcbmap fails %d\n", error); #endif return (error); } } /* * If the new length is not a multiple of the cluster size then we * must zero the tail end of the new last cluster in case it * becomes part of the file again because of a seek. */ if ((boff = length & pmp->pm_crbomask) != 0) { if (isadir) { bn = cntobn(pmp, eofentry); error = bread(pmp->pm_devvp, de_bn2kb(pmp, bn), pmp->pm_bpcluster, B_MODIFY, &bp); if (error) { #ifdef MSDOSFS_DEBUG printf("detrunc(): bread fails %d\n", error); #endif return (error); } memset((char *)bp->b_data + boff, 0, pmp->pm_bpcluster - boff); if (flags & IO_SYNC) bwrite(bp); else bdwrite(bp); } } /* * Write out the updated directory entry. Even if the update fails * we free the trailing clusters. */ dep->de_FileSize = length; if (!isadir) dep->de_flag |= DE_UPDATE|DE_MODIFIED; #ifdef MSDOSFS_DEBUG printf("detrunc(): allerror %d, eofentry %lu\n", allerror, eofentry); #endif /* * If we need to break the cluster chain for the file then do it * now. */ if (eofentry != (u_long)~0) { error = fatentry(FAT_GET_AND_SET, pmp, eofentry, &chaintofree, CLUST_EOFE); if (error) { #ifdef MSDOSFS_DEBUG printf("detrunc(): fatentry errors %d\n", error); #endif return (error); } } /* * Now free the clusters removed from the file because of the * truncation. */ if (chaintofree != 0 && !MSDOSFSEOF(chaintofree, pmp->pm_fatmask)) freeclusterchain(pmp, chaintofree); return (allerror); }
struct msdosfsmount * msdosfs_mount(struct vnode *devvp, int flags) { struct msdosfsmount *pmp = NULL; struct buf *bp; union bootsector *bsp; struct byte_bpb33 *b33; struct byte_bpb50 *b50; struct byte_bpb710 *b710; uint8_t SecPerClust; int ronly = 0, error, tmp; int bsize; struct msdos_options *m = devvp->fs->fs_specific; uint64_t psize = m->create_size; unsigned secsize = 512; DPRINTF(("%s(bread 0)\n", __func__)); if ((error = bread(devvp, 0, secsize, NULL, 0, &bp)) != 0) goto error_exit; bsp = (union bootsector *)bp->b_data; b33 = (struct byte_bpb33 *)bsp->bs33.bsBPB; b50 = (struct byte_bpb50 *)bsp->bs50.bsBPB; b710 = (struct byte_bpb710 *)bsp->bs710.bsBPB; if (!(flags & MSDOSFSMNT_GEMDOSFS)) { if (bsp->bs50.bsBootSectSig0 != BOOTSIG0 || bsp->bs50.bsBootSectSig1 != BOOTSIG1) { DPRINTF(("bootsig0 %d bootsig1 %d\n", bsp->bs50.bsBootSectSig0, bsp->bs50.bsBootSectSig1)); error = EINVAL; goto error_exit; } bsize = 0; } else bsize = 512; pmp = ecalloc(1, sizeof *pmp); /* * Compute several useful quantities from the bpb in the * bootsector. Copy in the dos 5 variant of the bpb then fix up * the fields that are different between dos 5 and dos 3.3. */ SecPerClust = b50->bpbSecPerClust; pmp->pm_BytesPerSec = getushort(b50->bpbBytesPerSec); pmp->pm_ResSectors = getushort(b50->bpbResSectors); pmp->pm_FATs = b50->bpbFATs; pmp->pm_RootDirEnts = getushort(b50->bpbRootDirEnts); pmp->pm_Sectors = getushort(b50->bpbSectors); pmp->pm_FATsecs = getushort(b50->bpbFATsecs); pmp->pm_SecPerTrack = getushort(b50->bpbSecPerTrack); pmp->pm_Heads = getushort(b50->bpbHeads); pmp->pm_Media = b50->bpbMedia; DPRINTF(("%s(BytesPerSec=%u, ResSectors=%u, FATs=%d, RootDirEnts=%u, " "Sectors=%u, FATsecs=%lu, SecPerTrack=%u, Heads=%u, Media=%u)\n", __func__, pmp->pm_BytesPerSec, pmp->pm_ResSectors, pmp->pm_FATs, pmp->pm_RootDirEnts, pmp->pm_Sectors, pmp->pm_FATsecs, pmp->pm_SecPerTrack, pmp->pm_Heads, pmp->pm_Media)); if (!(flags & MSDOSFSMNT_GEMDOSFS)) { /* XXX - We should probably check more values here */ if (!pmp->pm_BytesPerSec || !SecPerClust || pmp->pm_SecPerTrack > 63) { DPRINTF(("bytespersec %d secperclust %d " "secpertrack %d\n", pmp->pm_BytesPerSec, SecPerClust, pmp->pm_SecPerTrack)); error = EINVAL; goto error_exit; } } if (pmp->pm_Sectors == 0) { pmp->pm_HiddenSects = getulong(b50->bpbHiddenSecs); pmp->pm_HugeSectors = getulong(b50->bpbHugeSectors); } else { pmp->pm_HiddenSects = getushort(b33->bpbHiddenSecs); pmp->pm_HugeSectors = pmp->pm_Sectors; } if (pmp->pm_RootDirEnts == 0) { unsigned short vers = getushort(b710->bpbFSVers); /* * Some say that bsBootSectSig[23] must be zero, but * Windows does not require this and some digital cameras * do not set these to zero. Therefore, do not insist. */ if (pmp->pm_Sectors || pmp->pm_FATsecs || vers) { DPRINTF(("sectors %d fatsecs %lu vers %d\n", pmp->pm_Sectors, pmp->pm_FATsecs, vers)); error = EINVAL; goto error_exit; } pmp->pm_fatmask = FAT32_MASK; pmp->pm_fatmult = 4; pmp->pm_fatdiv = 1; pmp->pm_FATsecs = getulong(b710->bpbBigFATsecs); /* mirrorring is enabled if the FATMIRROR bit is not set */ if ((getushort(b710->bpbExtFlags) & FATMIRROR) == 0) pmp->pm_flags |= MSDOSFS_FATMIRROR; else pmp->pm_curfat = getushort(b710->bpbExtFlags) & FATNUM; } else pmp->pm_flags |= MSDOSFS_FATMIRROR; if (flags & MSDOSFSMNT_GEMDOSFS) { if (FAT32(pmp)) { DPRINTF(("FAT32 for GEMDOS\n")); /* * GEMDOS doesn't know FAT32. */ error = EINVAL; goto error_exit; } /* * Check a few values (could do some more): * - logical sector size: power of 2, >= block size * - sectors per cluster: power of 2, >= 1 * - number of sectors: >= 1, <= size of partition */ if ( (SecPerClust == 0) || (SecPerClust & (SecPerClust - 1)) || (pmp->pm_BytesPerSec < bsize) || (pmp->pm_BytesPerSec & (pmp->pm_BytesPerSec - 1)) || (pmp->pm_HugeSectors == 0) || (pmp->pm_HugeSectors * (pmp->pm_BytesPerSec / bsize) > psize)) { DPRINTF(("consistency checks for GEMDOS\n")); error = EINVAL; goto error_exit; } /* * XXX - Many parts of the msdosfs driver seem to assume that * the number of bytes per logical sector (BytesPerSec) will * always be the same as the number of bytes per disk block * Let's pretend it is. */ tmp = pmp->pm_BytesPerSec / bsize; pmp->pm_BytesPerSec = bsize; pmp->pm_HugeSectors *= tmp; pmp->pm_HiddenSects *= tmp; pmp->pm_ResSectors *= tmp; pmp->pm_Sectors *= tmp; pmp->pm_FATsecs *= tmp; SecPerClust *= tmp; } /* Check that fs has nonzero FAT size */ if (pmp->pm_FATsecs == 0) { DPRINTF(("FATsecs is 0\n")); error = EINVAL; goto error_exit; } pmp->pm_fatblk = pmp->pm_ResSectors; if (FAT32(pmp)) { pmp->pm_rootdirblk = getulong(b710->bpbRootClust); pmp->pm_firstcluster = pmp->pm_fatblk + (pmp->pm_FATs * pmp->pm_FATsecs); pmp->pm_fsinfo = getushort(b710->bpbFSInfo); } else { pmp->pm_rootdirblk = pmp->pm_fatblk + (pmp->pm_FATs * pmp->pm_FATsecs); pmp->pm_rootdirsize = (pmp->pm_RootDirEnts * sizeof(struct direntry) + pmp->pm_BytesPerSec - 1) / pmp->pm_BytesPerSec;/* in sectors */ pmp->pm_firstcluster = pmp->pm_rootdirblk + pmp->pm_rootdirsize; } pmp->pm_nmbrofclusters = (pmp->pm_HugeSectors - pmp->pm_firstcluster) / SecPerClust; pmp->pm_maxcluster = pmp->pm_nmbrofclusters + 1; pmp->pm_fatsize = pmp->pm_FATsecs * pmp->pm_BytesPerSec; if (flags & MSDOSFSMNT_GEMDOSFS) { if (pmp->pm_nmbrofclusters <= (0xff0 - 2)) { pmp->pm_fatmask = FAT12_MASK; pmp->pm_fatmult = 3; pmp->pm_fatdiv = 2; } else { pmp->pm_fatmask = FAT16_MASK; pmp->pm_fatmult = 2; pmp->pm_fatdiv = 1; } } else if (pmp->pm_fatmask == 0) { if (pmp->pm_maxcluster <= ((CLUST_RSRVD - CLUST_FIRST) & FAT12_MASK)) { /* * This will usually be a floppy disk. This size makes * sure that one FAT entry will not be split across * multiple blocks. */ pmp->pm_fatmask = FAT12_MASK; pmp->pm_fatmult = 3; pmp->pm_fatdiv = 2; } else { pmp->pm_fatmask = FAT16_MASK; pmp->pm_fatmult = 2; pmp->pm_fatdiv = 1; } } if (FAT12(pmp)) pmp->pm_fatblocksize = 3 * pmp->pm_BytesPerSec; else pmp->pm_fatblocksize = MAXBSIZE; pmp->pm_fatblocksec = pmp->pm_fatblocksize / pmp->pm_BytesPerSec; pmp->pm_bnshift = ffs(pmp->pm_BytesPerSec) - 1; /* * Compute mask and shift value for isolating cluster relative byte * offsets and cluster numbers from a file offset. */ pmp->pm_bpcluster = SecPerClust * pmp->pm_BytesPerSec; pmp->pm_crbomask = pmp->pm_bpcluster - 1; pmp->pm_cnshift = ffs(pmp->pm_bpcluster) - 1; DPRINTF(("%s(fatmask=%lu, fatmult=%u, fatdiv=%u, fatblocksize=%lu, " "fatblocksec=%lu, bnshift=%lu, pbcluster=%lu, crbomask=%lu, " "cnshift=%lu)\n", __func__, pmp->pm_fatmask, pmp->pm_fatmult, pmp->pm_fatdiv, pmp->pm_fatblocksize, pmp->pm_fatblocksec, pmp->pm_bnshift, pmp->pm_bpcluster, pmp->pm_crbomask, pmp->pm_cnshift)); /* * Check for valid cluster size * must be a power of 2 */ if (pmp->pm_bpcluster ^ (1 << pmp->pm_cnshift)) { DPRINTF(("bpcluster %lu cnshift %lu\n", pmp->pm_bpcluster, pmp->pm_cnshift)); error = EINVAL; goto error_exit; } /* * Release the bootsector buffer. */ brelse(bp, BC_AGE); bp = NULL; /* * Check FSInfo. */ if (pmp->pm_fsinfo) { struct fsinfo *fp; /* * XXX If the fsinfo block is stored on media with * 2KB or larger sectors, is the fsinfo structure * padded at the end or in the middle? */ DPRINTF(("%s(bread %lu)\n", __func__, (unsigned long)de_bn2kb(pmp, pmp->pm_fsinfo))); if ((error = bread(devvp, de_bn2kb(pmp, pmp->pm_fsinfo), pmp->pm_BytesPerSec, NULL, 0, &bp)) != 0) goto error_exit; fp = (struct fsinfo *)bp->b_data; if (!memcmp(fp->fsisig1, "RRaA", 4) && !memcmp(fp->fsisig2, "rrAa", 4) && !memcmp(fp->fsisig3, "\0\0\125\252", 4) && !memcmp(fp->fsisig4, "\0\0\125\252", 4)) pmp->pm_nxtfree = getulong(fp->fsinxtfree); else pmp->pm_fsinfo = 0; brelse(bp, 0); bp = NULL; } /* * Check and validate (or perhaps invalidate?) the fsinfo structure? * XXX */ if (pmp->pm_fsinfo) { if ((pmp->pm_nxtfree == 0xffffffffUL) || (pmp->pm_nxtfree > pmp->pm_maxcluster)) pmp->pm_fsinfo = 0; } /* * Allocate memory for the bitmap of allocated clusters, and then * fill it in. */ pmp->pm_inusemap = ecalloc(sizeof(*pmp->pm_inusemap), ((pmp->pm_maxcluster + N_INUSEBITS) / N_INUSEBITS)); /* * fillinusemap() needs pm_devvp. */ pmp->pm_dev = 0; pmp->pm_devvp = devvp; /* * Have the inuse map filled in. */ if ((error = fillinusemap(pmp)) != 0) { DPRINTF(("fillinusemap %d\n", error)); goto error_exit; } /* * Finish up. */ if (ronly) pmp->pm_flags |= MSDOSFSMNT_RONLY; else pmp->pm_fmod = 1; /* * If we ever do quotas for DOS filesystems this would be a place * to fill in the info in the msdosfsmount structure. You dolt, * quotas on dos filesystems make no sense because files have no * owners on dos filesystems. of course there is some empty space * in the directory entry where we could put uid's and gid's. */ return pmp; error_exit: if (bp) brelse(bp, BC_AGE); if (pmp) { if (pmp->pm_inusemap) free(pmp->pm_inusemap); free(pmp); } errno = error; return pmp; }