void adjust(struct inodesc *idesc, short lcnt) { struct ext2fs_dinode *dp; dp = ginode(idesc->id_number); if (fs2h16(dp->e2di_nlink) == lcnt) { if (linkup(idesc->id_number, (ino_t)0) == 0) clri(idesc, "UNREF", 0); } else { pwarn("LINK COUNT %s", (lfdir == idesc->id_number) ? lfname : ((fs2h16(dp->e2di_mode) & IFMT) == IFDIR ? "DIR" : "FILE")); pinode(idesc->id_number); printf(" COUNT %d SHOULD BE %d", fs2h16(dp->e2di_nlink), fs2h16(dp->e2di_nlink) - lcnt); if (preen) { if (lcnt < 0) { printf("\n"); pfatal("LINK COUNT INCREASING"); } printf(" (ADJUSTED)\n"); } if (preen || reply("ADJUST") == 1) { dp->e2di_nlink = h2fs16(fs2h16(dp->e2di_nlink) - lcnt); inodirty(); } } }
static int mkentry(struct inodesc *idesc) { struct ext2fs_direct *dirp = idesc->id_dirp; struct ext2fs_direct newent; int newlen, oldlen; newent.e2d_type = 0; /* XXX gcc */ newent.e2d_namlen = strlen(idesc->id_name); if (sblock.e2fs.e2fs_rev > E2FS_REV0 && (sblock.e2fs.e2fs_features_incompat & EXT2F_INCOMPAT_FTYPE)) newent.e2d_type = inot2ext2dt(typemap[idesc->id_parent]); newlen = EXT2FS_DIRSIZ(newent.e2d_namlen); if (dirp->e2d_ino != 0) oldlen = EXT2FS_DIRSIZ(dirp->e2d_namlen); else oldlen = 0; if (fs2h16(dirp->e2d_reclen) - oldlen < newlen) return (KEEPON); newent.e2d_reclen = h2fs16(fs2h16(dirp->e2d_reclen) - oldlen); dirp->e2d_reclen = h2fs16(oldlen); dirp = (struct ext2fs_direct *)(((char *)dirp) + oldlen); dirp->e2d_ino = h2fs32(idesc->id_parent); /* ino to be entered is in id_parent */ dirp->e2d_reclen = newent.e2d_reclen; dirp->e2d_namlen = newent.e2d_namlen; dirp->e2d_type = newent.e2d_type; memcpy(dirp->e2d_name, idesc->id_name, (size_t)(dirp->e2d_namlen)); return (ALTERED|STOP); }
/* * get next entry in a directory. */ static struct ext2fs_direct * fsck_readdir(struct inodesc *idesc) { struct ext2fs_direct *dp, *ndp; struct bufarea *bp; long size, blksiz, fix, dploc; blksiz = idesc->id_numfrags * sblock.e2fs_bsize; bp = getdirblk(idesc->id_blkno, blksiz); if (idesc->id_loc % sblock.e2fs_bsize == 0 && idesc->id_filesize > 0 && idesc->id_loc < blksiz) { dp = (struct ext2fs_direct *)(bp->b_un.b_buf + idesc->id_loc); if (dircheck(idesc, dp)) goto dpok; if (idesc->id_fix == IGNORE) return (0); fix = dofix(idesc, "DIRECTORY CORRUPTED"); bp = getdirblk(idesc->id_blkno, blksiz); dp = (struct ext2fs_direct *)(bp->b_un.b_buf + idesc->id_loc); dp->e2d_reclen = h2fs16(sblock.e2fs_bsize); dp->e2d_ino = 0; dp->e2d_namlen = 0; dp->e2d_type = 0; dp->e2d_name[0] = '\0'; if (fix) dirty(bp); idesc->id_loc += sblock.e2fs_bsize; idesc->id_filesize -= sblock.e2fs_bsize; return (dp); } dpok: if (idesc->id_filesize <= 0 || idesc->id_loc >= blksiz) return NULL; dploc = idesc->id_loc; dp = (struct ext2fs_direct *)(bp->b_un.b_buf + dploc); idesc->id_loc += fs2h16(dp->e2d_reclen); idesc->id_filesize -= fs2h16(dp->e2d_reclen); if ((idesc->id_loc % sblock.e2fs_bsize) == 0) return (dp); ndp = (struct ext2fs_direct *)(bp->b_un.b_buf + idesc->id_loc); if (idesc->id_loc < blksiz && idesc->id_filesize > 0 && dircheck(idesc, ndp) == 0) { size = sblock.e2fs_bsize - (idesc->id_loc % sblock.e2fs_bsize); idesc->id_loc += size; idesc->id_filesize -= size; if (idesc->id_fix == IGNORE) return (0); fix = dofix(idesc, "DIRECTORY CORRUPTED"); bp = getdirblk(idesc->id_blkno, blksiz); dp = (struct ext2fs_direct *)(bp->b_un.b_buf + dploc); dp->e2d_reclen = h2fs16(fs2h16(dp->e2d_reclen) + size); if (fix) dirty(bp); } return (dp); }
/* * allocate a new directory */ int allocdir(ino_t parent, ino_t request, int mode) { ino_t ino; struct ext2fs_dinode *dp; struct bufarea *bp; struct ext2fs_dirtemplate *dirp; ino = allocino(request, IFDIR|mode); dirhead.dot_reclen = h2fs16(12); /* XXX */ dirhead.dotdot_reclen = h2fs16(sblock.e2fs_bsize - 12); /* XXX */ dirhead.dot_namlen = 1; if (sblock.e2fs.e2fs_rev > E2FS_REV0 && (sblock.e2fs.e2fs_features_incompat & EXT2F_INCOMPAT_FTYPE)) dirhead.dot_type = EXT2_FT_DIR; else dirhead.dot_type = 0; dirhead.dotdot_namlen = 2; if (sblock.e2fs.e2fs_rev > E2FS_REV0 && (sblock.e2fs.e2fs_features_incompat & EXT2F_INCOMPAT_FTYPE)) dirhead.dotdot_type = EXT2_FT_DIR; else dirhead.dotdot_type = 0; dirp = &dirhead; dirp->dot_ino = h2fs32(ino); dirp->dotdot_ino = h2fs32(parent); dp = ginode(ino); bp = getdirblk(fs2h32(dp->e2di_blocks[0]), sblock.e2fs_bsize); if (bp->b_errs) { freeino(ino); return (0); } memcpy(bp->b_un.b_buf, dirp, sizeof(struct ext2fs_dirtemplate)); dirty(bp); dp->e2di_nlink = h2fs16(2); inodirty(); if (ino == EXT2_ROOTINO) { lncntp[ino] = fs2h16(dp->e2di_nlink); cacheino(dp, ino); return(ino); } if (statemap[parent] != DSTATE && statemap[parent] != DFOUND) { freeino(ino); return (0); } cacheino(dp, ino); statemap[ino] = statemap[parent]; if (statemap[ino] == DSTATE) { lncntp[ino] = fs2h16(dp->e2di_nlink); lncntp[parent]++; } dp = ginode(parent); dp->e2di_nlink = h2fs16(fs2h16(dp->e2di_nlink) + 1); inodirty(); return (ino); }
__compactcall void ext2fs_ls(struct open_file *f, const char *pattern) { struct file *fp = (struct file *)f->f_fsdata; size_t block_size = fp->f_fs->e2fs_bsize; char *buf; size_t buf_size; lsentry_t *names = NULL; fp->f_seekp = 0; while (fp->f_seekp < (off_t)fp->f_di.e2di_size) { struct ext2fs_direct *dp, *edp; int rc = buf_read_file(f, &buf, &buf_size); if (rc) goto out; if (buf_size != block_size || buf_size == 0) goto out; dp = (struct ext2fs_direct *)buf; edp = (struct ext2fs_direct *)(buf + buf_size); for (; dp < edp; dp = (void *)((char *)dp + fs2h16(dp->e2d_reclen))) { const char *t; if (fs2h16(dp->e2d_reclen) <= 0) goto out; if (fs2h32(dp->e2d_ino) == 0) continue; if (dp->e2d_type >= NELEM(typestr) || !(t = typestr[dp->e2d_type])) { /* * This does not handle "old" * filesystems properly. On little * endian machines, we get a bogus * type name if the namlen matches a * valid type identifier. We could * check if we read namlen "0" and * handle this case specially, if * there were a pressing need... */ printf("bad dir entry\n"); goto out; } lsadd(&names, pattern, dp->e2d_name, strlen(dp->e2d_name), fs2h32(dp->e2d_ino), t); } fp->f_seekp += buf_size; } lsprint(names); out: lsfree(names); }
/* * changed so that it confirms to ext2fs_check_dir_entry */ static int ext2fs_dirbadentry(struct vnode *dp, struct ext2fs_direct *de, int entryoffsetinblock) { struct ufsmount *ump = VFSTOUFS(dp->v_mount); int dirblksiz = ump->um_dirblksiz; const char *error_msg = NULL; int reclen = fs2h16(de->e2d_reclen); int namlen = de->e2d_namlen; if (reclen < EXT2FS_DIRSIZ(1)) /* e2d_namlen = 1 */ error_msg = "rec_len is smaller than minimal"; else if (reclen % 4 != 0) error_msg = "rec_len % 4 != 0"; else if (namlen > EXT2FS_MAXNAMLEN) error_msg = "namlen > EXT2FS_MAXNAMLEN"; else if (reclen < EXT2FS_DIRSIZ(namlen)) error_msg = "reclen is too small for name_len"; else if (entryoffsetinblock + reclen > dirblksiz) error_msg = "directory entry across blocks"; else if (fs2h32(de->e2d_ino) > VTOI(dp)->i_e2fs->e2fs.e2fs_icount) error_msg = "inode out of bounds"; if (error_msg != NULL) { printf( "bad directory entry: %s\n" "offset=%d, inode=%lu, rec_len=%d, name_len=%d \n", error_msg, entryoffsetinblock, (unsigned long) fs2h32(de->e2d_ino), reclen, namlen); panic("ext2fs_dirbadentry"); } return error_msg == NULL ? 0 : 1; }
/* * Sanity check the disk vnode content, and copy it over to inode structure. */ static int ext2fs_loadvnode_content(struct m_ext2fs *fs, ino_t ino, struct buf *bp, struct inode *ip) { struct ext2fs_dinode *din; int error = 0; din = (struct ext2fs_dinode *)((char *)bp->b_data + (ino_to_fsbo(fs, ino) * EXT2_DINODE_SIZE(fs))); /* sanity checks - inode data NOT byteswapped at this point */ if (EXT2_DINODE_FITS(din, e2di_extra_isize, EXT2_DINODE_SIZE(fs)) && (EXT2_DINODE_SIZE(fs) - EXT2_REV0_DINODE_SIZE) < fs2h16(din->e2di_extra_isize)) { printf("ext2fs: inode %"PRIu64" bad extra_isize %u", ino, din->e2di_extra_isize); error = EINVAL; goto bad; } /* everything allright, proceed with copy */ if (ip->i_din.e2fs_din == NULL) ip->i_din.e2fs_din = kmem_alloc(EXT2_DINODE_SIZE(fs), KM_SLEEP); e2fs_iload(din, ip->i_din.e2fs_din, EXT2_DINODE_SIZE(fs)); ext2fs_set_inode_guid(ip); bad: return error; }
/* * Verify that a directory entry is valid. * This is a superset of the checks made in the kernel. */ int dircheck(struct inodesc *idesc, struct ext2fs_direct *dp) { int size; char *cp; int spaceleft; u_int16_t reclen = fs2h16(dp->e2d_reclen); spaceleft = sblock.e2fs_bsize - (idesc->id_loc % sblock.e2fs_bsize); if (fs2h32(dp->e2d_ino) > maxino || reclen == 0 || reclen > spaceleft || (reclen & 0x3) != 0) return (0); if (dp->e2d_ino == 0) return (1); if (sblock.e2fs.e2fs_rev < E2FS_REV1 || (sblock.e2fs.e2fs_features_incompat & EXT2F_INCOMPAT_FTYPE) == 0) if (dp->e2d_type != 0) return (1); size = EXT2FS_DIRSIZ(dp->e2d_namlen); if (reclen < size || idesc->id_filesize < size /* || dp->e2d_namlen > EXT2FS_MAXNAMLEN */) return (0); for (cp = dp->e2d_name, size = 0; size < dp->e2d_namlen; size++) if (*cp == '\0' || (*cp++ == '/')) return (0); return (1); }
/* * Remove a directory entry after a call to namei, using * the parameters which it left in nameidata. The entry * dp->i_offset contains the offset into the directory of the * entry to be eliminated. The dp->i_count field contains the * size of the previous record in the directory. If this * is 0, the first entry is being deleted, so we need only * zero the inode number to mark the entry as free. If the * entry is not the first in the directory, we must reclaim * the space of the now empty record by adding the record size * to the size of the previous entry. */ int ext2fs_dirremove(struct vnode *dvp, struct componentname *cnp) { struct inode *dp; struct ext2fs_direct *ep; struct buf *bp; int error; dp = VTOI(dvp); if (dp->i_count == 0) { /* * First entry in block: set d_ino to zero. */ error = ext2fs_bufatoff(dp, (off_t)dp->i_offset, (char **)&ep, &bp); if (error != 0) return (error); ep->e2d_ino = 0; error = VOP_BWRITE(bp); dp->i_flag |= IN_CHANGE | IN_UPDATE; return (error); } /* * Collapse new free space into previous entry. */ error = ext2fs_bufatoff(dp, (off_t)(dp->i_offset - dp->i_count), (char **)&ep, &bp); if (error != 0) return (error); ep->e2d_reclen = h2fs16(fs2h16(ep->e2d_reclen) + dp->i_reclen); error = VOP_BWRITE(bp); dp->i_flag |= IN_CHANGE | IN_UPDATE; return (error); }
void inossize(struct ext2fs_dinode *dp, u_int64_t size) { if ((fs2h16(dp->e2di_mode) & IFMT) == IFREG) { dp->e2di_size_high = h2fs32(size >> 32); if (size > INT32_MAX) if (!setlarge()) return; } else if (size > INT32_MAX) {
u_int64_t inosize(struct ext2fs_dinode *dp) { u_int64_t size = fs2h32(dp->e2di_size); if ((fs2h16(dp->e2di_mode) & IFMT) == IFREG) size |= (u_int64_t)fs2h32(dp->e2di_size_high) << 32; if (size > INT32_MAX) (void)setlarge(); return size; }
int ftypeok(struct ext2fs_dinode *dp) { switch (fs2h16(dp->e2di_mode) & IFMT) { case IFDIR: case IFREG: case IFBLK: case IFCHR: case IFLNK: case IFSOCK: case IFIFO: return (1); default: if (debug) printf("bad file type 0%o\n", fs2h16(dp->e2di_mode)); return (0); } }
/* * free a directory inode */ static void freedir(ino_t ino, ino_t parent) { struct ext2fs_dinode *dp; if (ino != parent) { dp = ginode(parent); dp->e2di_nlink = h2fs16(fs2h16(dp->e2di_nlink) - 1); inodirty(); } freeino(ino); }
/* * Search a directory for a name and return its * inode number. */ static int search_directory(const char *name, int length, struct open_file *f, ino32_t *inumber_p) { struct file *fp = (struct file *)f->f_fsdata; struct ext2fs_direct *dp; struct ext2fs_direct *edp; char *buf; size_t buf_size; int namlen; int rc; fp->f_seekp = 0; /* XXX should handle LARGEFILE */ while (fp->f_seekp < (off_t)fp->f_di.e2di_size) { rc = buf_read_file(f, &buf, &buf_size); if (rc) return rc; dp = (struct ext2fs_direct *)buf; edp = (struct ext2fs_direct *)(buf + buf_size); for (; dp < edp; dp = (void *)((char *)dp + fs2h16(dp->e2d_reclen))) { if (fs2h16(dp->e2d_reclen) <= 0) break; if (fs2h32(dp->e2d_ino) == (ino32_t)0) continue; namlen = dp->e2d_namlen; if (namlen == length && !memcmp(name, dp->e2d_name, length)) { /* found entry */ *inumber_p = fs2h32(dp->e2d_ino); return 0; } } fp->f_seekp += buf_size; } return ENOENT; }
static int ext2fs_checksb(struct ext2fs *fs, int ronly) { uint32_t u32; if (fs2h16(fs->e2fs_magic) != E2FS_MAGIC) { return (EINVAL); /* XXX needs translation */ } if (fs2h32(fs->e2fs_rev) > E2FS_REV1) { #ifdef DIAGNOSTIC printf("ext2fs: unsupported revision number: %x\n", fs2h32(fs->e2fs_rev)); #endif return (EINVAL); /* XXX needs translation */ } if (fs2h32(fs->e2fs_log_bsize) > 2) { /* block size = 1024|2048|4096 */ #ifdef DIAGNOSTIC printf("ext2fs: bad block size: %d " "(expected <= 2 for ext2 fs)\n", fs2h32(fs->e2fs_log_bsize)); #endif return (EINVAL); /* XXX needs translation */ } if (fs2h32(fs->e2fs_rev) > E2FS_REV0) { char buf[256]; if (fs2h32(fs->e2fs_first_ino) != EXT2_FIRSTINO) { printf("ext2fs: unsupported first inode position\n"); return (EINVAL); /* XXX needs translation */ } u32 = fs2h32(fs->e2fs_features_incompat) & ~EXT2F_INCOMPAT_SUPP; if (u32) { snprintb(buf, sizeof(buf), EXT2F_INCOMPAT_BITS, u32); printf("ext2fs: unsupported incompat features: %s\n", buf); return EINVAL; /* XXX needs translation */ } u32 = fs2h32(fs->e2fs_features_rocompat) & ~EXT2F_ROCOMPAT_SUPP; if (!ronly && u32) { snprintb(buf, sizeof(buf), EXT2F_ROCOMPAT_BITS, u32); printf("ext2fs: unsupported ro-incompat features: %s\n", buf); return EROFS; /* XXX needs translation */ } } return (0); }
/* * Check if a directory is empty or not. * Inode supplied must be locked. * * Using a struct dirtemplate here is not precisely * what we want, but better than using a struct ext2fs_direct. * * NB: does not handle corrupted directories. */ int ext2fs_dirempty(struct inode *ip, ino_t parentino, kauth_cred_t cred) { off_t off; struct ext2fs_dirtemplate dbuf; struct ext2fs_direct *dp = (struct ext2fs_direct *)&dbuf; int error, namlen; size_t count; #define MINDIRSIZ (sizeof (struct ext2fs_dirtemplate) / 2) for (off = 0; off < ext2fs_size(ip); off += fs2h16(dp->e2d_reclen)) { error = ufs_bufio(UIO_READ, ITOV(ip), (void *)dp, MINDIRSIZ, off, IO_NODELOCKED, cred, &count, NULL); /* * Since we read MINDIRSIZ, residual must * be 0 unless we're at end of file. */ if (error || count != 0) return (0); /* avoid infinite loops */ if (dp->e2d_reclen == 0) return (0); /* skip empty entries */ if (dp->e2d_ino == 0) continue; /* accept only "." and ".." */ namlen = dp->e2d_namlen; if (namlen > 2) return (0); if (dp->e2d_name[0] != '.') return (0); /* * At this point namlen must be 1 or 2. * 1 implies ".", 2 implies ".." if second * char is also "." */ if (namlen == 1) continue; if (dp->e2d_name[1] == '.' && fs2h32(dp->e2d_ino) == parentino) continue; return (0); } return (1); }
/* * Scan each entry in a directory block. */ int dirscan(struct inodesc *idesc) { struct ext2fs_direct *dp; struct bufarea *bp; int dsize, n; long blksiz; char *dbuf = NULL; if ((dbuf = malloc(sblock.e2fs_bsize)) == NULL) err(8, "Can't allocate directory block"); if (idesc->id_type != DATA) errexit("wrong type to dirscan %d", idesc->id_type); if (idesc->id_entryno == 0 && (idesc->id_filesize & (sblock.e2fs_bsize - 1)) != 0) idesc->id_filesize = roundup(idesc->id_filesize, sblock.e2fs_bsize); blksiz = idesc->id_numfrags * sblock.e2fs_bsize; if (chkrange(idesc->id_blkno, idesc->id_numfrags)) { idesc->id_filesize -= blksiz; free(dbuf); return (SKIP); } idesc->id_loc = 0; for (dp = fsck_readdir(idesc); dp != NULL; dp = fsck_readdir(idesc)) { dsize = fs2h16(dp->e2d_reclen); memcpy(dbuf, dp, (size_t)dsize); idesc->id_dirp = (struct ext2fs_direct *)dbuf; if ((n = (*idesc->id_func)(idesc)) & ALTERED) { bp = getdirblk(idesc->id_blkno, blksiz); memcpy(bp->b_un.b_buf + idesc->id_loc - dsize, dbuf, (size_t)dsize); dirty(bp); sbdirty(); } if (n & STOP) { free(dbuf); return (n); } } free(dbuf); return (idesc->id_filesize > 0 ? KEEPON : STOP); }
void fileerror(ino_t cwd, ino_t ino, const char *errmesg) { struct ext2fs_dinode *dp; char pathbuf[MAXPATHLEN + 1]; pwarn("%s ", errmesg); pinode(ino); printf("\n"); getpathname(pathbuf, sizeof(pathbuf), cwd, ino); if ((ino < EXT2_FIRSTINO && ino != EXT2_ROOTINO) || ino > maxino) { pfatal("NAME=%s\n", pathbuf); return; } dp = ginode(ino); if (ftypeok(dp)) pfatal("%s=%s\n", (fs2h16(dp->e2di_mode) & IFMT) == IFDIR ? "DIR" : "FILE", pathbuf); else pfatal("NAME=%s\n", pathbuf); }
(void)ext2fs_setsize(ip, 0); memset(ip->i_e2fs_blocks, 0, sizeof(ip->i_e2fs_blocks)); } *vpp = vp; return (0); } static int ext2fs_checksb(struct ext2fs *fs, int ronly) { if (fs2h16(fs->e2fs_magic) != E2FS_MAGIC) { return (EINVAL); /* XXX needs translation */ } if (fs2h32(fs->e2fs_rev) > E2FS_REV1) { #ifdef DIAGNOSTIC printf("Ext2 fs: unsupported revision number: %x\n", fs2h32(fs->e2fs_rev)); #endif return (EINVAL); /* XXX needs translation */ } if (fs2h32(fs->e2fs_log_bsize) > 2) { /* block size = 1024|2048|4096 */ #ifdef DIAGNOSTIC printf("Ext2 fs: bad block size: %d " "(expected <= 2 for ext2 fs)\n", fs2h32(fs->e2fs_log_bsize)); #endif return (EINVAL); /* XXX needs translation */ } if (fs2h32(fs->e2fs_rev) > E2FS_REV0) { if (fs2h32(fs->e2fs_first_ino) != EXT2_FIRSTINO) { printf("Ext2 fs: unsupported first inode position\n"); return (EINVAL); /* XXX needs translation */ } if (fs2h32(fs->e2fs_features_incompat) & ~EXT2F_INCOMPAT_SUPP) { printf("Ext2 fs: unsupported optional feature\n"); return (EINVAL); /* XXX needs translation */
/* * Remove a directory entry after a call to namei, using * the auxiliary results it provided. The entry * ulr_offset contains the offset into the directory of the * entry to be eliminated. The ulr_count field contains the * size of the previous record in the directory. If this * is 0, the first entry is being deleted, so we need only * zero the inode number to mark the entry as free. If the * entry is not the first in the directory, we must reclaim * the space of the now empty record by adding the record size * to the size of the previous entry. */ int ext2fs_dirremove(struct vnode *dvp, const struct ufs_lookup_results *ulr, struct componentname *cnp) { struct inode *dp; struct ext2fs_direct *ep; struct buf *bp; int error; dp = VTOI(dvp); if (ulr->ulr_count == 0) { /* * First entry in block: set d_ino to zero. */ error = ext2fs_blkatoff(dvp, (off_t)ulr->ulr_offset, (void *)&ep, &bp); if (error != 0) return (error); ep->e2d_ino = 0; error = VOP_BWRITE(bp->b_vp, bp); dp->i_flag |= IN_CHANGE | IN_UPDATE; return (error); } /* * Collapse new free space into previous entry. */ error = ext2fs_blkatoff(dvp, (off_t)(ulr->ulr_offset - ulr->ulr_count), (void *)&ep, &bp); if (error != 0) return (error); ep->e2d_reclen = h2fs16(fs2h16(ep->e2d_reclen) + ulr->ulr_reclen); error = VOP_BWRITE(bp->b_vp, bp); dp->i_flag |= IN_CHANGE | IN_UPDATE; return (error); }
/* * Vnode op for reading directories. * * Convert the on-disk entries to <sys/dirent.h> entries. * the problem is that the conversion will blow up some entries by four bytes, * so it can't be done in place. This is too bad. Right now the conversion is * done entry by entry, the converted entry is sent via uiomove. * * XXX allocate a buffer, convert as many entries as possible, then send * the whole buffer to uiomove */ int ext2fs_readdir(void *v) { struct vop_readdir_args /* { struct vnode *a_vp; struct uio *a_uio; kauth_cred_t a_cred; int **a_eofflag; off_t **a_cookies; int ncookies; } */ *ap = v; struct uio *uio = ap->a_uio; int error; size_t e2fs_count, readcnt; struct vnode *vp = ap->a_vp; struct m_ext2fs *fs = VTOI(vp)->i_e2fs; struct ext2fs_direct *dp; struct dirent *dstd; struct uio auio; struct iovec aiov; void *dirbuf; off_t off = uio->uio_offset; off_t *cookies = NULL; int nc = 0, ncookies = 0; int e2d_reclen; if (vp->v_type != VDIR) return (ENOTDIR); e2fs_count = uio->uio_resid; /* Make sure we don't return partial entries. */ e2fs_count -= (uio->uio_offset + e2fs_count) & (fs->e2fs_bsize -1); if (e2fs_count <= 0) return (EINVAL); auio = *uio; auio.uio_iov = &aiov; auio.uio_iovcnt = 1; aiov.iov_len = e2fs_count; auio.uio_resid = e2fs_count; UIO_SETUP_SYSSPACE(&auio); dirbuf = kmem_alloc(e2fs_count, KM_SLEEP); dstd = kmem_zalloc(sizeof(struct dirent), KM_SLEEP); if (ap->a_ncookies) { nc = e2fs_count / _DIRENT_MINSIZE((struct dirent *)0); ncookies = nc; cookies = malloc(sizeof (off_t) * ncookies, M_TEMP, M_WAITOK); *ap->a_cookies = cookies; } aiov.iov_base = dirbuf; error = UFS_BUFRD(ap->a_vp, &auio, 0, ap->a_cred); if (error == 0) { readcnt = e2fs_count - auio.uio_resid; for (dp = (struct ext2fs_direct *)dirbuf; (char *)dp < (char *)dirbuf + readcnt; ) { e2d_reclen = fs2h16(dp->e2d_reclen); if (e2d_reclen == 0) { error = EIO; break; } ext2fs_dirconv2ffs(dp, dstd); if(dstd->d_reclen > uio->uio_resid) { break; } error = uiomove(dstd, dstd->d_reclen, uio); if (error != 0) { break; } off = off + e2d_reclen; if (cookies != NULL) { *cookies++ = off; if (--ncookies <= 0){ break; /* out of cookies */ } } /* advance dp */ dp = (struct ext2fs_direct *) ((char *)dp + e2d_reclen); } /* we need to correct uio_offset */ uio->uio_offset = off; } kmem_free(dirbuf, e2fs_count); kmem_free(dstd, sizeof(*dstd)); *ap->a_eofflag = ext2fs_size(VTOI(ap->a_vp)) <= uio->uio_offset; if (ap->a_ncookies) { if (error) { free(*ap->a_cookies, M_TEMP); *ap->a_ncookies = 0; *ap->a_cookies = NULL; } else *ap->a_ncookies = nc - ncookies; } return (error); }
/* * ext2fs_rename_recalculate_fulr: If we have just entered a directory * into dvp at tulr, and we were about to remove one at fulr for an * entry named fcnp, fulr may be invalid. So, if necessary, * recalculate it. */ static int ext2fs_rename_recalculate_fulr(struct vnode *dvp, struct ufs_lookup_results *fulr, const struct ufs_lookup_results *tulr, const struct componentname *fcnp) { struct mount *mp; struct ufsmount *ump; /* XXX int is a silly type for this; blame ufsmount::um_dirblksiz. */ int dirblksiz; doff_t search_start, search_end; doff_t offset; /* Offset of entry we're examining. */ struct buf *bp; /* I/O block we're examining. */ char *dirbuf; /* Pointer into directory at search_start. */ struct ext2fs_direct *ep; /* Pointer to the entry we're examining. */ /* XXX direct::d_reclen is 16-bit; * ufs_lookup_results::ulr_reclen is 32-bit. Blah. */ uint32_t reclen; /* Length of the entry we're examining. */ uint32_t prev_reclen; /* Length of the preceding entry. */ int error; KASSERT(dvp != NULL); KASSERT(dvp->v_mount != NULL); KASSERT(VTOI(dvp) != NULL); KASSERT(fulr != NULL); KASSERT(tulr != NULL); KASSERT(fulr != tulr); KASSERT(ext2fs_rename_ulr_overlap_p(fulr, tulr)); mp = dvp->v_mount; ump = VFSTOUFS(mp); KASSERT(ump != NULL); KASSERT(ump == VTOI(dvp)->i_ump); dirblksiz = ump->um_dirblksiz; KASSERT(0 < dirblksiz); KASSERT((dirblksiz & (dirblksiz - 1)) == 0); /* A directory block may not span across multiple I/O blocks. */ KASSERT(dirblksiz <= mp->mnt_stat.f_iosize); /* Find the bounds of the search. */ search_start = tulr->ulr_offset; KASSERT(fulr->ulr_reclen < (EXT2FS_MAXDIRSIZE - fulr->ulr_offset)); search_end = (fulr->ulr_offset + fulr->ulr_reclen); /* Compaction must happen only within a directory block. (*) */ KASSERT(search_start <= search_end); KASSERT((search_end - (search_start &~ (dirblksiz - 1))) <= dirblksiz); dirbuf = NULL; bp = NULL; error = ext2fs_blkatoff(dvp, (off_t)search_start, &dirbuf, &bp); if (error) return error; KASSERT(dirbuf != NULL); KASSERT(bp != NULL); /* * Guarantee we sha'n't go past the end of the buffer we got. * dirbuf is bp->b_data + (search_start & (iosize - 1)), and * the valid range is [bp->b_data, bp->b_data + bp->b_bcount). */ KASSERT((search_end - search_start) <= (bp->b_bcount - (search_start & (mp->mnt_stat.f_iosize - 1)))); prev_reclen = fulr->ulr_count; offset = search_start; /* * Search from search_start to search_end for the entry matching * fcnp, which must be there because we found it before and it * should only at most have moved earlier. */ for (;;) { KASSERT(search_start <= offset); KASSERT(offset < search_end); /* * Examine the directory entry at offset. */ ep = (struct ext2fs_direct *) (dirbuf + (offset - search_start)); reclen = fs2h16(ep->e2d_reclen); if (ep->e2d_ino == 0) goto next; /* Entry is unused. */ if (fs2h32(ep->e2d_ino) == UFS_WINO) goto next; /* Entry is whiteout. */ if (fcnp->cn_namelen != ep->e2d_namlen) goto next; /* Wrong name length. */ if (memcmp(ep->e2d_name, fcnp->cn_nameptr, fcnp->cn_namelen)) goto next; /* Wrong name. */ /* Got it! */ break; next: if (! ((reclen < search_end) && (offset < (search_end - reclen)))) { brelse(bp, 0); return EIO; /* XXX Panic? What? */ } /* We may not move past the search end. */ KASSERT(reclen < search_end); KASSERT(offset < (search_end - reclen)); /* * We may not move across a directory block boundary; * see (*) above. */ KASSERT((offset &~ (dirblksiz - 1)) == ((offset + reclen) &~ (dirblksiz - 1))); prev_reclen = reclen; offset += reclen; } /* * Found the entry. Record where. */ fulr->ulr_offset = offset; fulr->ulr_reclen = reclen; /* * Record the preceding record length, but not if we're at the * start of a directory block. */ fulr->ulr_count = ((offset & (dirblksiz - 1))? prev_reclen : 0); brelse(bp, 0); return 0; }
int linkup(ino_t orphan, ino_t parentdir) { struct ext2fs_dinode *dp; int lostdir; ino_t oldlfdir; struct inodesc idesc; char tempname[BUFSIZ]; memset(&idesc, 0, sizeof(struct inodesc)); dp = ginode(orphan); lostdir = (fs2h16(dp->e2di_mode) & IFMT) == IFDIR; pwarn("UNREF %s ", lostdir ? "DIR" : "FILE"); pinode(orphan); if (preen && inosize(dp) == 0) return (0); if (preen) printf(" (RECONNECTED)\n"); else if (reply("RECONNECT") == 0) return (0); if (lfdir == 0) { dp = ginode(EXT2_ROOTINO); idesc.id_name = lfname; idesc.id_type = DATA; idesc.id_func = findino; idesc.id_number = EXT2_ROOTINO; if ((ckinode(dp, &idesc) & FOUND) != 0) { lfdir = idesc.id_parent; } else { pwarn("NO lost+found DIRECTORY"); if (preen || reply("CREATE")) { lfdir = allocdir(EXT2_ROOTINO, (ino_t)0, lfmode); if (lfdir != 0) { if (makeentry(EXT2_ROOTINO, lfdir, lfname) != 0) { if (preen) printf(" (CREATED)\n"); } else { freedir(lfdir, EXT2_ROOTINO); lfdir = 0; if (preen) printf("\n"); } } } } if (lfdir == 0) { pfatal("SORRY. CANNOT CREATE lost+found DIRECTORY"); printf("\n\n"); return (0); } } dp = ginode(lfdir); if ((fs2h16(dp->e2di_mode) & IFMT) != IFDIR) { pfatal("lost+found IS NOT A DIRECTORY"); if (reply("REALLOCATE") == 0) return (0); oldlfdir = lfdir; if ((lfdir = allocdir(EXT2_ROOTINO, (ino_t)0, lfmode)) == 0) { pfatal("SORRY. CANNOT CREATE lost+found DIRECTORY\n\n"); return (0); } if ((changeino(EXT2_ROOTINO, lfname, lfdir) & ALTERED) == 0) { pfatal("SORRY. CANNOT CREATE lost+found DIRECTORY\n\n"); return (0); } inodirty(); idesc.id_type = ADDR; idesc.id_func = pass4check; idesc.id_number = oldlfdir; adjust(&idesc, lncntp[oldlfdir] + 1); lncntp[oldlfdir] = 0; dp = ginode(lfdir); } if (statemap[lfdir] != DFOUND) { pfatal("SORRY. NO lost+found DIRECTORY\n\n"); return (0); } (void)lftempname(tempname, orphan); if (makeentry(lfdir, orphan, tempname) == 0) { pfatal("SORRY. NO SPACE IN lost+found DIRECTORY"); printf("\n\n"); return (0); } lncntp[orphan]--; if (lostdir) { if ((changeino(orphan, "..", lfdir) & ALTERED) == 0 && parentdir != (ino_t)-1) (void)makeentry(orphan, lfdir, ".."); dp = ginode(lfdir); dp->e2di_nlink = h2fs16(fs2h16(dp->e2di_nlink) +1); inodirty(); lncntp[lfdir]++; pwarn("DIR I=%llu CONNECTED. ", (unsigned long long)orphan); if (parentdir != (ino_t)-1) printf("PARENT WAS I=%llu\n", (unsigned long long)parentdir); if (preen == 0) printf("\n"); } return (1); }
static int pass2check(struct inodesc *idesc) { struct ext2fs_direct *dirp = idesc->id_dirp; struct inoinfo *inp; int n, entrysize, ret = 0; struct ext2fs_dinode *dp; const char *errmsg; struct ext2fs_direct proto; char namebuf[MAXPATHLEN + 1]; char pathbuf[MAXPATHLEN + 1]; /* * check for "." */ if (idesc->id_entryno != 0) goto chk1; if (fs2h32(dirp->e2d_ino) != 0 && dirp->e2d_namlen == 1 && dirp->e2d_name[0] == '.') { if (fs2h32(dirp->e2d_ino) != idesc->id_number) { direrror(idesc->id_number, "BAD INODE NUMBER FOR '.'"); dirp->e2d_ino = h2fs32(idesc->id_number); if (reply("FIX") == 1) ret |= ALTERED; } if (sblock.e2fs.e2fs_rev > E2FS_REV0 && (sblock.e2fs.e2fs_features_incompat & EXT2F_INCOMPAT_FTYPE) && (dirp->e2d_type != EXT2_FT_DIR)) { direrror(idesc->id_number, "BAD TYPE VALUE FOR '.'"); dirp->e2d_type = EXT2_FT_DIR; if (reply("FIX") == 1) ret |= ALTERED; } goto chk1; } direrror(idesc->id_number, "MISSING '.'"); proto.e2d_ino = h2fs32(idesc->id_number); proto.e2d_namlen = 1; if (sblock.e2fs.e2fs_rev > E2FS_REV0 && (sblock.e2fs.e2fs_features_incompat & EXT2F_INCOMPAT_FTYPE)) proto.e2d_type = EXT2_FT_DIR; else proto.e2d_type = 0; (void)strlcpy(proto.e2d_name, ".", sizeof(proto.e2d_name)); entrysize = EXT2FS_DIRSIZ(proto.e2d_namlen); if (fs2h32(dirp->e2d_ino) != 0 && strcmp(dirp->e2d_name, "..") != 0) { pfatal("CANNOT FIX, FIRST ENTRY IN DIRECTORY CONTAINS %s\n", dirp->e2d_name); } else if (fs2h16(dirp->e2d_reclen) < entrysize) { pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '.'\n"); } else if (fs2h16(dirp->e2d_reclen) < 2 * entrysize) { proto.e2d_reclen = dirp->e2d_reclen; memcpy(dirp, &proto, (size_t)entrysize); if (reply("FIX") == 1) ret |= ALTERED; } else { n = fs2h16(dirp->e2d_reclen) - entrysize; proto.e2d_reclen = h2fs16(entrysize); memcpy(dirp, &proto, (size_t)entrysize); idesc->id_entryno++; lncntp[fs2h32(dirp->e2d_ino)]--; dirp = (struct ext2fs_direct *)((char *)(dirp) + entrysize); memset(dirp, 0, (size_t)n); dirp->e2d_reclen = h2fs16(n); if (reply("FIX") == 1) ret |= ALTERED; } chk1: if (idesc->id_entryno > 1) goto chk2; inp = getinoinfo(idesc->id_number); proto.e2d_ino = h2fs32(inp->i_parent); proto.e2d_namlen = 2; if (sblock.e2fs.e2fs_rev > E2FS_REV0 && (sblock.e2fs.e2fs_features_incompat & EXT2F_INCOMPAT_FTYPE)) proto.e2d_type = EXT2_FT_DIR; else proto.e2d_type = 0; (void)strlcpy(proto.e2d_name, "..", sizeof(proto.e2d_name)); entrysize = EXT2FS_DIRSIZ(2); if (idesc->id_entryno == 0) { n = EXT2FS_DIRSIZ(dirp->e2d_namlen); if (fs2h16(dirp->e2d_reclen) < n + entrysize) goto chk2; proto.e2d_reclen = h2fs16(fs2h16(dirp->e2d_reclen) - n); dirp->e2d_reclen = h2fs16(n); idesc->id_entryno++; lncntp[fs2h32(dirp->e2d_ino)]--; dirp = (struct ext2fs_direct *)((char *)(dirp) + n); memset(dirp, 0, (size_t)fs2h16(proto.e2d_reclen)); dirp->e2d_reclen = proto.e2d_reclen; } if (fs2h32(dirp->e2d_ino) != 0 && dirp->e2d_namlen == 2 && strncmp(dirp->e2d_name, "..", 2) == 0) { inp->i_dotdot = fs2h32(dirp->e2d_ino); if (sblock.e2fs.e2fs_rev > E2FS_REV0 && (sblock.e2fs.e2fs_features_incompat & EXT2F_INCOMPAT_FTYPE) && dirp->e2d_type != EXT2_FT_DIR) { direrror(idesc->id_number, "BAD TYPE VALUE FOR '..'"); dirp->e2d_type = EXT2_FT_DIR; if (reply("FIX") == 1) ret |= ALTERED; } goto chk2; } if (fs2h32(dirp->e2d_ino) != 0 && dirp->e2d_namlen == 1 && strncmp(dirp->e2d_name, ".", 1) != 0) { fileerror(inp->i_parent, idesc->id_number, "MISSING '..'"); pfatal("CANNOT FIX, SECOND ENTRY IN DIRECTORY CONTAINS %s\n", dirp->e2d_name); inp->i_dotdot = (ino_t)-1; } else if (fs2h16(dirp->e2d_reclen) < entrysize) { fileerror(inp->i_parent, idesc->id_number, "MISSING '..'"); pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '..'\n"); inp->i_dotdot = (ino_t)-1; } else if (inp->i_parent != 0) { /* * We know the parent, so fix now. */ inp->i_dotdot = inp->i_parent; fileerror(inp->i_parent, idesc->id_number, "MISSING '..'"); proto.e2d_reclen = dirp->e2d_reclen; memcpy(dirp, &proto, (size_t)entrysize); if (reply("FIX") == 1) ret |= ALTERED; } idesc->id_entryno++; if (fs2h32(dirp->e2d_ino) != 0) lncntp[fs2h32(dirp->e2d_ino)]--; return (ret|KEEPON); chk2: if (fs2h32(dirp->e2d_ino) == 0) return (ret|KEEPON); if (dirp->e2d_namlen <= 2 && dirp->e2d_name[0] == '.' && idesc->id_entryno >= 2) { if (dirp->e2d_namlen == 1) { direrror(idesc->id_number, "EXTRA '.' ENTRY"); dirp->e2d_ino = 0; if (reply("FIX") == 1) ret |= ALTERED; return (KEEPON | ret); } if (dirp->e2d_name[1] == '.') { direrror(idesc->id_number, "EXTRA '..' ENTRY"); dirp->e2d_ino = 0; if (reply("FIX") == 1) ret |= ALTERED; return (KEEPON | ret); } } idesc->id_entryno++; n = 0; if (fs2h32(dirp->e2d_ino) > maxino || (fs2h32(dirp->e2d_ino) < EXT2_FIRSTINO && fs2h32(dirp->e2d_ino) != EXT2_ROOTINO)) { fileerror(idesc->id_number, fs2h32(dirp->e2d_ino), "I OUT OF RANGE"); n = reply("REMOVE"); } else { again: switch (statemap[fs2h32(dirp->e2d_ino)]) { case USTATE: if (idesc->id_entryno <= 2) break; fileerror(idesc->id_number, fs2h32(dirp->e2d_ino), "UNALLOCATED"); n = reply("REMOVE"); break; case DCLEAR: case FCLEAR: if (idesc->id_entryno <= 2) break; if (statemap[fs2h32(dirp->e2d_ino)] == FCLEAR) errmsg = "DUP/BAD"; else if (!preen) errmsg = "ZERO LENGTH DIRECTORY"; else { n = 1; break; } fileerror(idesc->id_number, fs2h32(dirp->e2d_ino), errmsg); if ((n = reply("REMOVE")) == 1) break; dp = ginode(fs2h32(dirp->e2d_ino)); statemap[fs2h32(dirp->e2d_ino)] = (fs2h16(dp->e2di_mode) & IFMT) == IFDIR ? DSTATE : FSTATE; lncntp[fs2h32(dirp->e2d_ino)] = fs2h16(dp->e2di_nlink); goto again; case DSTATE: case DFOUND: inp = getinoinfo(fs2h32(dirp->e2d_ino)); if (inp->i_parent != 0 && idesc->id_entryno > 2) { getpathname(pathbuf, sizeof(pathbuf), idesc->id_number, idesc->id_number); getpathname(namebuf, sizeof(namebuf), fs2h32(dirp->e2d_ino), fs2h32(dirp->e2d_ino)); pwarn("%s %s %s\n", pathbuf, "IS AN EXTRANEOUS HARD LINK TO DIRECTORY", namebuf); if (preen) printf(" (IGNORED)\n"); else if ((n = reply("REMOVE")) == 1) break; } if (idesc->id_entryno > 2) inp->i_parent = idesc->id_number; /* fall through */ case FSTATE: if (sblock.e2fs.e2fs_rev > E2FS_REV0 && (sblock.e2fs.e2fs_features_incompat & EXT2F_INCOMPAT_FTYPE) && dirp->e2d_type != inot2ext2dt(typemap[fs2h32(dirp->e2d_ino)])) { dirp->e2d_type = inot2ext2dt(typemap[fs2h32(dirp->e2d_ino)]); fileerror(idesc->id_number, fs2h32(dirp->e2d_ino), "BAD TYPE VALUE"); if (reply("FIX") == 1) ret |= ALTERED; } lncntp[fs2h32(dirp->e2d_ino)]--; break; default: errexit("BAD STATE %d FOR INODE I=%d", statemap[fs2h32(dirp->e2d_ino)], fs2h32(dirp->e2d_ino)); } } if (n == 0) return (ret|KEEPON); dirp->e2d_ino = 0; return (ret|KEEPON|ALTERED); }
void pass2(void) { struct ext2fs_dinode *dp; struct inoinfo **inpp, *inp; struct inoinfo **inpend; struct inodesc curino; struct ext2fs_dinode dino; char pathbuf[MAXPATHLEN + 1]; switch (statemap[EXT2_ROOTINO]) { case USTATE: pfatal("ROOT INODE UNALLOCATED"); if (reply("ALLOCATE") == 0) exit(FSCK_EXIT_CHECK_FAILED); if (allocdir(EXT2_ROOTINO, EXT2_ROOTINO, 0755) != EXT2_ROOTINO) errexit("CANNOT ALLOCATE ROOT INODE"); break; case DCLEAR: pfatal("DUPS/BAD IN ROOT INODE"); if (reply("REALLOCATE")) { freeino(EXT2_ROOTINO); if (allocdir(EXT2_ROOTINO, EXT2_ROOTINO, 0755) != EXT2_ROOTINO) errexit("CANNOT ALLOCATE ROOT INODE"); break; } if (reply("CONTINUE") == 0) exit(FSCK_EXIT_CHECK_FAILED); break; case FSTATE: case FCLEAR: pfatal("ROOT INODE NOT DIRECTORY"); if (reply("REALLOCATE")) { freeino(EXT2_ROOTINO); if (allocdir(EXT2_ROOTINO, EXT2_ROOTINO, 0755) != EXT2_ROOTINO) errexit("CANNOT ALLOCATE ROOT INODE"); break; } if (reply("FIX") == 0) exit(FSCK_EXIT_CHECK_FAILED); dp = ginode(EXT2_ROOTINO); dp->e2di_mode = h2fs16((fs2h16(dp->e2di_mode) & ~IFMT) | IFDIR); inodirty(); break; case DSTATE: break; default: errexit("BAD STATE %d FOR ROOT INODE", statemap[EXT2_ROOTINO]); } /* * Sort the directory list into disk block order. */ qsort((char *)inpsort, (size_t)inplast, sizeof *inpsort, blksort); /* * Check the integrity of each directory. */ memset(&curino, 0, sizeof(struct inodesc)); curino.id_type = DATA; curino.id_func = pass2check; inpend = &inpsort[inplast]; for (inpp = inpsort; inpp < inpend; inpp++) { inp = *inpp; if (inp->i_isize == 0) continue; if (inp->i_isize < MINDIRSIZE) { direrror(inp->i_number, "DIRECTORY TOO SHORT"); inp->i_isize = roundup(MINDIRSIZE, sblock.e2fs_bsize); if (reply("FIX") == 1) { dp = ginode(inp->i_number); inossize(dp, inp->i_isize); inodirty(); } } else if ((inp->i_isize & (sblock.e2fs_bsize - 1)) != 0) { getpathname(pathbuf, sizeof(pathbuf), inp->i_number, inp->i_number); pwarn("DIRECTORY %s: LENGTH %lu NOT MULTIPLE OF %d", pathbuf, (u_long)inp->i_isize, sblock.e2fs_bsize); if (preen) printf(" (ADJUSTED)\n"); inp->i_isize = roundup(inp->i_isize, sblock.e2fs_bsize); if (preen || reply("ADJUST") == 1) { dp = ginode(inp->i_number); inossize(dp, inp->i_isize); inodirty(); } } memset(&dino, 0, sizeof(struct ext2fs_dinode)); dino.e2di_mode = h2fs16(IFDIR); inossize(&dino, inp->i_isize); memcpy(&dino.e2di_blocks[0], &inp->i_blks[0], (size_t)inp->i_numblks); curino.id_number = inp->i_number; curino.id_parent = inp->i_parent; (void)ckinode(&dino, &curino); } /* * Now that the parents of all directories have been found, * make another pass to verify the value of `..' */ for (inpp = inpsort; inpp < inpend; inpp++) { inp = *inpp; if (inp->i_parent == 0 || inp->i_isize == 0) continue; if (inp->i_dotdot == inp->i_parent || inp->i_dotdot == (ino_t)-1) continue; if (inp->i_dotdot == 0) { inp->i_dotdot = inp->i_parent; fileerror(inp->i_parent, inp->i_number, "MISSING '..'"); if (reply("FIX") == 0) continue; (void)makeentry(inp->i_number, inp->i_parent, ".."); lncntp[inp->i_parent]--; continue; } fileerror(inp->i_parent, inp->i_number, "BAD INODE NUMBER FOR '..'"); if (reply("FIX") == 0) continue; lncntp[inp->i_dotdot]++; lncntp[inp->i_parent]--; inp->i_dotdot = inp->i_parent; (void)changeino(inp->i_number, "..", inp->i_parent); } /* * Mark all the directories that can be found from the root. */ propagate(); }
/* * Convert a component of a pathname into a pointer to a locked inode. * This is a very central and rather complicated routine. * If the file system is not maintained in a strict tree hierarchy, * this can result in a deadlock situation (see comments in code below). * * The cnp->cn_nameiop argument is LOOKUP, CREATE, RENAME, or DELETE depending * on whether the name is to be looked up, created, renamed, or deleted. * When CREATE, RENAME, or DELETE is specified, information usable in * creating, renaming, or deleting a directory entry may be calculated. * If flag has LOCKPARENT or'ed into it and the target of the pathname * exists, lookup returns both the target and its parent directory locked. * When creating or renaming and LOCKPARENT is specified, the target may * not be ".". When deleting and LOCKPARENT is specified, the target may * be "."., but the caller must check to ensure it does an vrele and vput * instead of two vputs. * * Overall outline of ext2fs_lookup: * * check accessibility of directory * look for name in cache, if found, then if at end of path * and deleting or creating, drop it, else return name * search for name in directory, to found or notfound * notfound: * if creating, return locked directory, leaving info on available slots * else return error * found: * if at end of path and deleting, return information to allow delete * if at end of path and rewriting (RENAME and LOCKPARENT), lock target * inode and return info to allow rewrite * if not at end, add name to cache; if at end and neither creating * nor deleting, add name to cache */ int ext2fs_lookup(void *v) { struct vop_lookup_args *ap = v; struct vnode *vdp; /* vnode for directory being searched */ struct inode *dp; /* inode for directory being searched */ struct buf *bp; /* a buffer of directory entries */ struct ext2fs_direct *ep; /* the current directory entry */ int entryoffsetinblock; /* offset of ep in bp's buffer */ enum {NONE, COMPACT, FOUND} slotstatus; doff_t slotoffset; /* offset of area with free space */ int slotsize; /* size of area at slotoffset */ int slotfreespace; /* amount of space free in slot */ int slotneeded; /* size of the entry we're seeking */ int numdirpasses; /* strategy for directory search */ doff_t endsearch; /* offset to end directory search */ doff_t prevoff; /* prev entry dp->i_offset */ struct vnode *pdp; /* saved dp during symlink work */ struct vnode *tdp; /* returned by VFS_VGET */ doff_t enduseful; /* pointer past last used dir slot */ u_long bmask; /* block offset mask */ int lockparent; /* 1 => lockparent flag is set */ int wantparent; /* 1 => wantparent or lockparent flag */ int namlen, error; struct vnode **vpp = ap->a_vpp; struct componentname *cnp = ap->a_cnp; struct ucred *cred = cnp->cn_cred; int flags = cnp->cn_flags; int nameiop = cnp->cn_nameiop; struct proc *p = cnp->cn_proc; int dirblksize = VTOI(ap->a_dvp)->i_e2fs->e2fs_bsize; bp = NULL; slotoffset = -1; *vpp = NULL; vdp = ap->a_dvp; dp = VTOI(vdp); lockparent = flags & LOCKPARENT; wantparent = flags & (LOCKPARENT|WANTPARENT); /* * Check accessiblity of directory. */ if ((error = VOP_ACCESS(vdp, VEXEC, 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 ((error = cache_lookup(vdp, vpp, cnp)) >= 0) return (error); /* * 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. */ slotstatus = FOUND; slotfreespace = slotsize = slotneeded = 0; if ((nameiop == CREATE || nameiop == RENAME) && (flags & ISLASTCN)) { slotstatus = NONE; slotneeded = EXT2FS_DIRSIZ(cnp->cn_namelen); } /* * If there is cached information on a previous search of * this directory, pick up where we last left off. * We cache only lookups as these are the most common * and have the greatest payoff. Caching CREATE has little * benefit as it usually must search the entire directory * to determine that the entry does not exist. Caching the * location of the last DELETE or RENAME has not reduced * profiling time and hence has been removed in the interest * of simplicity. */ bmask = VFSTOUFS(vdp->v_mount)->um_mountp->mnt_stat.f_iosize - 1; if (nameiop != LOOKUP || dp->i_diroff == 0 || dp->i_diroff >ext2fs_size(dp)) { entryoffsetinblock = 0; dp->i_offset = 0; numdirpasses = 1; } else { dp->i_offset = dp->i_diroff; if ((entryoffsetinblock = dp->i_offset & bmask) && (error = ext2fs_bufatoff(dp, (off_t)dp->i_offset, NULL, &bp))) return (error); numdirpasses = 2; } prevoff = dp->i_offset; endsearch = roundup(ext2fs_size(dp), dirblksize); enduseful = 0; searchloop: while (dp->i_offset < endsearch) { /* * If necessary, get the next directory block. */ if ((dp->i_offset & bmask) == 0) { if (bp != NULL) brelse(bp); error = ext2fs_bufatoff(dp, (off_t)dp->i_offset, NULL, &bp); if (error != 0) return (error); entryoffsetinblock = 0; } /* * If still looking for a slot, and at a dirblksize * boundary, have to start looking for free space again. */ if (slotstatus == NONE && (entryoffsetinblock & (dirblksize - 1)) == 0) { slotoffset = -1; slotfreespace = 0; } /* * Get pointer to next entry. * Full validation checks are slow, so we only check * enough to insure forward progress through the * directory. Complete checks can be run by patching * "dirchk" to be true. */ ep = (struct ext2fs_direct *) ((char *)bp->b_data + entryoffsetinblock); if (ep->e2d_reclen == 0 || (dirchk && ext2fs_dirbadentry(vdp, ep, entryoffsetinblock))) { int i; ufs_dirbad(dp, dp->i_offset, "mangled entry"); i = dirblksize - (entryoffsetinblock & (dirblksize - 1)); dp->i_offset += i; entryoffsetinblock += i; continue; } /* * If an appropriate sized slot has not yet been found, * check to see if one is available. Also accumulate space * in the current block so that we can determine if * compaction is viable. */ if (slotstatus != FOUND) { int size = fs2h16(ep->e2d_reclen); if (ep->e2d_ino != 0) size -= EXT2FS_DIRSIZ(ep->e2d_namlen); if (size > 0) { if (size >= slotneeded) { slotstatus = FOUND; slotoffset = dp->i_offset; slotsize = fs2h16(ep->e2d_reclen); } else if (slotstatus == NONE) { slotfreespace += size; if (slotoffset == -1) slotoffset = dp->i_offset; if (slotfreespace >= slotneeded) { slotstatus = COMPACT; slotsize = dp->i_offset + fs2h16(ep->e2d_reclen) - slotoffset; } } } } /* * Check for a name match. */ if (ep->e2d_ino) { namlen = ep->e2d_namlen; if (namlen == cnp->cn_namelen && !memcmp(cnp->cn_nameptr, ep->e2d_name, (unsigned)namlen)) { /* * Save directory entry's inode number and * reclen in ndp->ni_ufs area, and release * directory buffer. */ dp->i_ino = fs2h32(ep->e2d_ino); dp->i_reclen = fs2h16(ep->e2d_reclen); brelse(bp); goto found; } } prevoff = dp->i_offset; dp->i_offset += fs2h16(ep->e2d_reclen); entryoffsetinblock += fs2h16(ep->e2d_reclen); if (ep->e2d_ino) enduseful = dp->i_offset; } /* notfound: */ /* * If we started in the middle of the directory and failed * to find our target, we must check the beginning as well. */ if (numdirpasses == 2) { numdirpasses--; dp->i_offset = 0; endsearch = dp->i_diroff; goto searchloop; } if (bp != NULL) brelse(bp); /* * If creating, and at end of pathname and current * directory has not been removed, then can consider * allowing file to be created. */ if ((nameiop == CREATE || nameiop == RENAME) && (flags & ISLASTCN) && dp->i_e2fs_nlink != 0) { /* * Creation of files on a read-only mounted file system * is pointless, so don't proceed any further. */ if (vdp->v_mount->mnt_flag & MNT_RDONLY) return (EROFS); /* * Access for write is interpreted as allowing * creation of files in the directory. */ if ((error = VOP_ACCESS(vdp, VWRITE, cred)) != 0) return (error); /* * Return an indication of where the new directory * entry should be put. If we didn't find a slot, * then set dp->i_count to 0 indicating * that the new slot belongs at the end of the * directory. If we found a slot, then the new entry * can be put in the range from dp->i_offset to * dp->i_offset + dp->i_count. */ if (slotstatus == NONE) { dp->i_offset = roundup(ext2fs_size(dp), dirblksize); dp->i_count = 0; enduseful = dp->i_offset; } else { dp->i_offset = slotoffset; dp->i_count = slotsize; if (enduseful < slotoffset + slotsize) enduseful = slotoffset + slotsize; } dp->i_endoff = roundup(enduseful, dirblksize); dp->i_flag |= IN_CHANGE | IN_UPDATE; /* * 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. * The pathname buffer is saved so that the name * can be obtained later. * * NB - if the directory is unlocked, then this * information cannot be used. */ cnp->cn_flags |= SAVENAME; if (!lockparent) { VOP_UNLOCK(vdp, 0); cnp->cn_flags |= PDIRUNLOCK; } return (EJUSTRETURN); } /* * Insert name into cache (as non-existent) if appropriate. */ if ((cnp->cn_flags & MAKEENTRY) && nameiop != CREATE) cache_enter(vdp, *vpp, cnp); return (ENOENT); found: /* * Check that directory length properly reflects presence * of this entry. */ if (entryoffsetinblock + EXT2FS_DIRSIZ(ep->e2d_namlen) > ext2fs_size(dp)) { ufs_dirbad(dp, dp->i_offset, "i_size too small"); error = ext2fs_setsize(dp, entryoffsetinblock + EXT2FS_DIRSIZ(ep->e2d_namlen)); if (error) { brelse(bp); return(error); } dp->i_flag |= IN_CHANGE | IN_UPDATE; } /* * Found component in pathname. * If the final component of path name, save information * in the cache as to where the entry was found. */ if ((flags & ISLASTCN) && nameiop == LOOKUP) dp->i_diroff = dp->i_offset &~ (dirblksize - 1); /* * If deleting, and at end of pathname, return * parameters which can be used to remove file. * If the wantparent flag isn't set, we return only * the directory (in ndp->ni_dvp), otherwise we go * on and lock the inode, being careful with ".". */ if (nameiop == DELETE && (flags & ISLASTCN)) { /* * Write access to directory required to delete files. */ if ((error = VOP_ACCESS(vdp, VWRITE, cred)) != 0) return (error); /* * Return pointer to current entry in dp->i_offset, * and distance past previous entry (if there * is a previous entry in this block) in dp->i_count. * Save directory inode pointer in ndp->ni_dvp for dirremove(). */ if ((dp->i_offset & (dirblksize - 1)) == 0) dp->i_count = 0; else dp->i_count = dp->i_offset - prevoff; if (dp->i_number == dp->i_ino) { vref(vdp); *vpp = vdp; return (0); } if ((error = VFS_VGET(vdp->v_mount, dp->i_ino, &tdp)) != 0) return (error); /* * If directory is "sticky", then user must own * the directory, or the file in it, else she * may not delete it (unless she's root). This * implements append-only directories. */ if ((dp->i_e2fs_mode & ISVTX) && cred->cr_uid != 0 && cred->cr_uid != dp->i_e2fs_uid && VTOI(tdp)->i_e2fs_uid != cred->cr_uid) { vput(tdp); return (EPERM); } *vpp = tdp; if (!lockparent) { VOP_UNLOCK(vdp, 0); cnp->cn_flags |= PDIRUNLOCK; } 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 && wantparent && (flags & ISLASTCN)) { if ((error = VOP_ACCESS(vdp, VWRITE, cred)) != 0) return (error); /* * Careful about locking second inode. * This can only occur if the target is ".". */ if (dp->i_number == dp->i_ino) return (EISDIR); if ((error = VFS_VGET(vdp->v_mount, dp->i_ino, &tdp)) != 0) return (error); *vpp = tdp; cnp->cn_flags |= SAVENAME; if (!lockparent) { VOP_UNLOCK(vdp, 0); cnp->cn_flags |= PDIRUNLOCK; } 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, 0); /* race to get the inode */ cnp->cn_flags |= PDIRUNLOCK; if ((error = VFS_VGET(vdp->v_mount, dp->i_ino, &tdp)) != 0) { if (vn_lock(pdp, LK_EXCLUSIVE | LK_RETRY, p) == 0) cnp->cn_flags &= ~PDIRUNLOCK; return (error); } if (lockparent && (flags & ISLASTCN)) { if ((error = vn_lock(pdp, LK_EXCLUSIVE, p)) != 0) { vput(tdp); return (error); } cnp->cn_flags &= ~PDIRUNLOCK; } *vpp = tdp; } else if (dp->i_number == dp->i_ino) { vref(vdp); /* we want ourself, ie "." */ *vpp = vdp; } else { if ((error = VFS_VGET(vdp->v_mount, dp->i_ino, &tdp)) != 0) return (error); if (!lockparent || !(flags & ISLASTCN)) { VOP_UNLOCK(pdp, 0); cnp->cn_flags |= PDIRUNLOCK; } *vpp = tdp; } /* * Insert name into cache if appropriate. */ if (cnp->cn_flags & MAKEENTRY) cache_enter(vdp, *vpp, cnp); return (0); }
int setup(const char *dev) { long cg, asked, i; long bmapsize; struct disklabel *lp; off_t sizepb; struct stat statb; struct m_ext2fs proto; int doskipclean; u_int64_t maxfilesize; havesb = 0; fswritefd = -1; doskipclean = skipclean; if (stat(dev, &statb) < 0) { printf("Can't stat %s: %s\n", dev, strerror(errno)); return 0; } if (!S_ISCHR(statb.st_mode)) { pfatal("%s is not a character device", dev); if (reply("CONTINUE") == 0) return 0; } if ((fsreadfd = open(dev, O_RDONLY)) < 0) { printf("Can't open %s: %s\n", dev, strerror(errno)); return 0; } if (preen == 0) printf("** %s", dev); if (nflag || (fswritefd = open(dev, O_WRONLY)) < 0) { fswritefd = -1; if (preen) pfatal("NO WRITE ACCESS"); printf(" (NO WRITE)"); } if (preen == 0) printf("\n"); fsmodified = 0; lfdir = 0; initbarea(&sblk); initbarea(&asblk); sblk.b_un.b_buf = malloc(SBSIZE); asblk.b_un.b_buf = malloc(SBSIZE); if (sblk.b_un.b_buf == NULL || asblk.b_un.b_buf == NULL) errexit("cannot allocate space for superblock"); if ((lp = getdisklabel(NULL, fsreadfd)) != NULL) dev_bsize = secsize = lp->d_secsize; else dev_bsize = secsize = DEV_BSIZE; /* * Read in the superblock, looking for alternates if necessary */ if (readsb(1) == 0) { if (bflag || preen || calcsb(dev, fsreadfd, &proto) == 0) return 0; if (reply("LOOK FOR ALTERNATE SUPERBLOCKS") == 0) return 0; for (cg = 1; cg < proto.e2fs_ncg; cg++) { bflag = EXT2_FSBTODB(&proto, cg * proto.e2fs.e2fs_bpg + proto.e2fs.e2fs_first_dblock); if (readsb(0) != 0) break; } if (cg >= proto.e2fs_ncg) { printf("%s %s\n%s %s\n%s %s\n", "SEARCH FOR ALTERNATE SUPER-BLOCK", "FAILED. YOU MUST USE THE", "-b OPTION TO FSCK_FFS TO SPECIFY THE", "LOCATION OF AN ALTERNATE", "SUPER-BLOCK TO SUPPLY NEEDED", "INFORMATION; SEE fsck_ext2fs(8)."); return 0; } doskipclean = 0; pwarn("USING ALTERNATE SUPERBLOCK AT %d\n", bflag); } if (debug) printf("state = %d\n", sblock.e2fs.e2fs_state); if (sblock.e2fs.e2fs_state == E2FS_ISCLEAN) { if (doskipclean) { pwarn("%sile system is clean; not checking\n", preen ? "f" : "** F"); return -1; } if (!preen) pwarn("** File system is already clean\n"); } maxfsblock = sblock.e2fs.e2fs_bcount; maxino = sblock.e2fs_ncg * sblock.e2fs.e2fs_ipg; sizepb = sblock.e2fs_bsize; maxfilesize = sblock.e2fs_bsize * EXT2FS_NDADDR - 1; for (i = 0; i < EXT2FS_NIADDR; i++) { sizepb *= EXT2_NINDIR(&sblock); maxfilesize += sizepb; } /* * Check and potentially fix certain fields in the super block. */ if (/* (sblock.e2fs.e2fs_rbcount < 0) || */ (sblock.e2fs.e2fs_rbcount > sblock.e2fs.e2fs_bcount)) { pfatal("IMPOSSIBLE RESERVED BLOCK COUNT=%d IN SUPERBLOCK", sblock.e2fs.e2fs_rbcount); if (reply("SET TO DEFAULT") == 1) { sblock.e2fs.e2fs_rbcount = sblock.e2fs.e2fs_bcount * MINFREE / 100; sbdirty(); dirty(&asblk); } } if (sblock.e2fs.e2fs_bpg != sblock.e2fs.e2fs_fpg) { pfatal("WRONG FPG=%d (BPG=%d) IN SUPERBLOCK", sblock.e2fs.e2fs_fpg, sblock.e2fs.e2fs_bpg); return 0; } if (asblk.b_dirty && !bflag) { copyback_sb(&asblk); flush(fswritefd, &asblk); } /* * read in the summary info. */ sblock.e2fs_gd = malloc(sblock.e2fs_ngdb * sblock.e2fs_bsize); if (sblock.e2fs_gd == NULL) errexit("out of memory"); asked = 0; for (i = 0; i < sblock.e2fs_ngdb; i++) { if (bread(fsreadfd, (char *)&sblock.e2fs_gd[i * sblock.e2fs_bsize / sizeof(struct ext2_gd)], EXT2_FSBTODB(&sblock, ((sblock.e2fs_bsize > 1024) ? 0 : 1) + i + 1), sblock.e2fs_bsize) != 0 && !asked) { pfatal("BAD SUMMARY INFORMATION"); if (reply("CONTINUE") == 0) exit(FSCK_EXIT_CHECK_FAILED); asked++; } } /* * allocate and initialize the necessary maps */ bmapsize = roundup(howmany(maxfsblock, NBBY), sizeof(int16_t)); blockmap = calloc((unsigned int)bmapsize, sizeof(char)); if (blockmap == NULL) { printf("cannot alloc %u bytes for blockmap\n", (unsigned int)bmapsize); goto badsblabel; } statemap = calloc((unsigned int)(maxino + 2), sizeof(char)); if (statemap == NULL) { printf("cannot alloc %u bytes for statemap\n", (unsigned int)(maxino + 1)); goto badsblabel; } typemap = calloc((unsigned int)(maxino + 1), sizeof(char)); if (typemap == NULL) { printf("cannot alloc %u bytes for typemap\n", (unsigned int)(maxino + 1)); goto badsblabel; } lncntp = calloc((unsigned)(maxino + 1), sizeof(int16_t)); if (lncntp == NULL) { printf("cannot alloc %u bytes for lncntp\n", (unsigned int)((maxino + 1) * sizeof(int16_t))); goto badsblabel; } for (numdirs = 0, cg = 0; cg < sblock.e2fs_ncg; cg++) { numdirs += fs2h16(sblock.e2fs_gd[cg].ext2bgd_ndirs); } inplast = 0; listmax = numdirs + 10; inpsort = calloc((unsigned int)listmax, sizeof(struct inoinfo *)); inphead = calloc((unsigned int)numdirs, sizeof(struct inoinfo *)); if (inpsort == NULL || inphead == NULL) { printf("cannot alloc %u bytes for inphead\n", (unsigned int)(numdirs * sizeof(struct inoinfo *))); goto badsblabel; } bufinit(); return 1; badsblabel: ckfini(0); return 0; }
/* * Convert a component of a pathname into a pointer to a locked inode. * This is a very central and rather complicated routine. * If the file system is not maintained in a strict tree hierarchy, * this can result in a deadlock situation (see comments in code below). * * The cnp->cn_nameiop argument is LOOKUP, CREATE, RENAME, or DELETE depending * on whether the name is to be looked up, created, renamed, or deleted. * When CREATE, RENAME, or DELETE is specified, information usable in * creating, renaming, or deleting a directory entry may be calculated. * If flag has LOCKPARENT or'ed into it and the target of the pathname * exists, lookup returns both the target and its parent directory locked. * When creating or renaming and LOCKPARENT is specified, the target may * not be ".". When deleting and LOCKPARENT is specified, the target may * be "."., but the caller must check to ensure it does an vrele and vput * instead of two vputs. * * Overall outline of ext2fs_lookup: * * check accessibility of directory * look for name in cache, if found, then if at end of path * and deleting or creating, drop it, else return name * search for name in directory, to found or notfound * notfound: * if creating, return locked directory, leaving info on available slots * else return error * found: * if at end of path and deleting, return information to allow delete * if at end of path and rewriting (RENAME and LOCKPARENT), lock target * inode and return info to allow rewrite * if not at end, add name to cache; if at end and neither creating * nor deleting, add name to cache */ int ext2fs_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; /* vnode for directory being searched */ struct inode *dp = VTOI(vdp); /* inode for directory being searched */ struct buf *bp; /* a buffer of directory entries */ struct ext2fs_direct *ep; /* the current directory entry */ int entryoffsetinblock; /* offset of ep in bp's buffer */ enum {NONE, COMPACT, FOUND} slotstatus; doff_t slotoffset; /* offset of area with free space */ int slotsize; /* size of area at slotoffset */ int slotfreespace; /* amount of space free in slot */ int slotneeded; /* size of the entry we're seeking */ int numdirpasses; /* strategy for directory search */ doff_t endsearch; /* offset to end directory search */ doff_t prevoff; /* prev entry dp->i_offset */ struct vnode *tdp; /* returned by vcache_get */ doff_t enduseful; /* pointer past last used dir slot */ u_long bmask; /* block offset mask */ int namlen, error; struct vnode **vpp = ap->a_vpp; struct componentname *cnp = ap->a_cnp; kauth_cred_t cred = cnp->cn_cred; int flags; int nameiop = cnp->cn_nameiop; struct ufsmount *ump = dp->i_ump; int dirblksiz = ump->um_dirblksiz; ino_t foundino; struct ufs_lookup_results *results; flags = cnp->cn_flags; bp = NULL; slotoffset = -1; *vpp = NULL; /* * Produce the auxiliary lookup results into i_crap. Increment * its serial number so elsewhere we can tell if we're using * stale results. This should not be done this way. XXX. */ results = &dp->i_crap; dp->i_crapcounter++; /* * Check accessiblity of directory. */ if ((error = VOP_ACCESS(vdp, VEXEC, 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; } /* * 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. */ slotstatus = FOUND; slotfreespace = slotsize = slotneeded = 0; if ((nameiop == CREATE || nameiop == RENAME) && (flags & ISLASTCN)) { slotstatus = NONE; slotneeded = EXT2FS_DIRSIZ(cnp->cn_namelen); } /* * If there is cached information on a previous search of * this directory, pick up where we last left off. * We cache only lookups as these are the most common * and have the greatest payoff. Caching CREATE has little * benefit as it usually must search the entire directory * to determine that the entry does not exist. Caching the * location of the last DELETE or RENAME has not reduced * profiling time and hence has been removed in the interest * of simplicity. */ bmask = vdp->v_mount->mnt_stat.f_iosize - 1; if (nameiop != LOOKUP || results->ulr_diroff == 0 || results->ulr_diroff >= ext2fs_size(dp)) { entryoffsetinblock = 0; results->ulr_offset = 0; numdirpasses = 1; } else { results->ulr_offset = results->ulr_diroff; if ((entryoffsetinblock = results->ulr_offset & bmask) && (error = ext2fs_blkatoff(vdp, (off_t)results->ulr_offset, NULL, &bp))) return (error); numdirpasses = 2; namecache_count_2passes(); } prevoff = results->ulr_offset; endsearch = roundup(ext2fs_size(dp), dirblksiz); enduseful = 0; searchloop: while (results->ulr_offset < endsearch) { if (curcpu()->ci_schedstate.spc_flags & SPCF_SHOULDYIELD) preempt(); /* * If necessary, get the next directory block. */ if ((results->ulr_offset & bmask) == 0) { if (bp != NULL) brelse(bp, 0); error = ext2fs_blkatoff(vdp, (off_t)results->ulr_offset, NULL, &bp); if (error != 0) return (error); entryoffsetinblock = 0; } /* * If still looking for a slot, and at a dirblksize * boundary, have to start looking for free space again. */ if (slotstatus == NONE && (entryoffsetinblock & (dirblksiz - 1)) == 0) { slotoffset = -1; slotfreespace = 0; } /* * Get pointer to next entry. * Full validation checks are slow, so we only check * enough to insure forward progress through the * directory. Complete checks can be run by patching * "dirchk" to be true. */ KASSERT(bp != NULL); ep = (struct ext2fs_direct *) ((char *)bp->b_data + entryoffsetinblock); if (ep->e2d_reclen == 0 || (dirchk && ext2fs_dirbadentry(vdp, ep, entryoffsetinblock))) { int i; ufs_dirbad(dp, results->ulr_offset, "mangled entry"); i = dirblksiz - (entryoffsetinblock & (dirblksiz - 1)); results->ulr_offset += i; entryoffsetinblock += i; continue; } /* * If an appropriate sized slot has not yet been found, * check to see if one is available. Also accumulate space * in the current block so that we can determine if * compaction is viable. */ if (slotstatus != FOUND) { int size = fs2h16(ep->e2d_reclen); if (ep->e2d_ino != 0) size -= EXT2FS_DIRSIZ(ep->e2d_namlen); if (size > 0) { if (size >= slotneeded) { slotstatus = FOUND; slotoffset = results->ulr_offset; slotsize = fs2h16(ep->e2d_reclen); } else if (slotstatus == NONE) { slotfreespace += size; if (slotoffset == -1) slotoffset = results->ulr_offset; if (slotfreespace >= slotneeded) { slotstatus = COMPACT; slotsize = results->ulr_offset + fs2h16(ep->e2d_reclen) - slotoffset; } } } } /* * Check for a name match. */ if (ep->e2d_ino) { namlen = ep->e2d_namlen; if (namlen == cnp->cn_namelen && !memcmp(cnp->cn_nameptr, ep->e2d_name, (unsigned)namlen)) { /* * Save directory entry's inode number and * reclen in ndp->ni_ufs area, and release * directory buffer. */ foundino = fs2h32(ep->e2d_ino); results->ulr_reclen = fs2h16(ep->e2d_reclen); goto found; } } prevoff = results->ulr_offset; results->ulr_offset += fs2h16(ep->e2d_reclen); entryoffsetinblock += fs2h16(ep->e2d_reclen); if (ep->e2d_ino) enduseful = results->ulr_offset; } /* notfound: */ /* * If we started in the middle of the directory and failed * to find our target, we must check the beginning as well. */ if (numdirpasses == 2) { numdirpasses--; results->ulr_offset = 0; endsearch = results->ulr_diroff; goto searchloop; } if (bp != NULL) brelse(bp, 0); /* * If creating, and at end of pathname and current * directory has not been removed, then can consider * allowing file to be created. */ if ((nameiop == CREATE || nameiop == RENAME) && (flags & ISLASTCN) && dp->i_e2fs_nlink != 0) { /* * Access for write is interpreted as allowing * creation of files in the directory. */ error = VOP_ACCESS(vdp, VWRITE, cred); if (error) return (error); /* * Return an indication of where the new directory * entry should be put. If we didn't find a slot, * then set results->ulr_count to 0 indicating * that the new slot belongs at the end of the * directory. If we found a slot, then the new entry * can be put in the range from results->ulr_offset to * results->ulr_offset + results->ulr_count. */ if (slotstatus == NONE) { results->ulr_offset = roundup(ext2fs_size(dp), dirblksiz); results->ulr_count = 0; enduseful = results->ulr_offset; } else { results->ulr_offset = slotoffset; results->ulr_count = slotsize; if (enduseful < slotoffset + slotsize) enduseful = slotoffset + slotsize; } results->ulr_endoff = roundup(enduseful, dirblksiz); #if 0 dp->i_flag |= IN_CHANGE | IN_UPDATE; #endif /* * 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); } /* * Insert name into cache (as non-existent) if appropriate. */ if (nameiop != CREATE) { cache_enter(vdp, *vpp, cnp->cn_nameptr, cnp->cn_namelen, cnp->cn_flags); } return ENOENT; found: if (numdirpasses == 2) namecache_count_pass2(); /* * Check that directory length properly reflects presence * of this entry. */ if (results->ulr_offset + EXT2FS_DIRSIZ(ep->e2d_namlen) > ext2fs_size(dp)) { ufs_dirbad(dp, results->ulr_offset, "i_size too small"); error = ext2fs_setsize(dp, results->ulr_offset + EXT2FS_DIRSIZ(ep->e2d_namlen)); if (error) { brelse(bp, 0); return (error); } dp->i_flag |= IN_CHANGE | IN_UPDATE; uvm_vnp_setsize(vdp, ext2fs_size(dp)); } brelse(bp, 0); /* * Found component in pathname. * If the final component of path name, save information * in the cache as to where the entry was found. */ if ((flags & ISLASTCN) && nameiop == LOOKUP) results->ulr_diroff = results->ulr_offset &~ (dirblksiz - 1); /* * 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)) { /* * Return pointer to current entry in results->ulr_offset, * and distance past previous entry (if there * is a previous entry in this block) in results->ulr_count. * Save directory inode pointer in ndp->ni_dvp for dirremove(). */ if ((results->ulr_offset & (dirblksiz - 1)) == 0) results->ulr_count = 0; else results->ulr_count = results->ulr_offset - prevoff; if (dp->i_number == foundino) { vref(vdp); tdp = vdp; } else { error = vcache_get(vdp->v_mount, &foundino, sizeof(foundino), &tdp); if (error) return (error); } /* * Write access to directory required to delete files. */ if ((error = VOP_ACCESS(vdp, VWRITE, cred)) != 0) { vrele(tdp); return (error); } /* * If directory is "sticky", then user must own * the directory, or the file in it, else she * may not delete it (unless she's root). This * implements append-only directories. */ if (dp->i_e2fs_mode & ISVTX) { error = kauth_authorize_vnode(cred, KAUTH_VNODE_DELETE, tdp, vdp, genfs_can_sticky(cred, dp->i_uid, VTOI(tdp)->i_uid)); if (error) { vrele(tdp); return (EPERM); } } *vpp = tdp; 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)) { error = VOP_ACCESS(vdp, VWRITE, cred); if (error) return (error); /* * Careful about locking second inode. * This can only occur if the target is ".". */ if (dp->i_number == foundino) return (EISDIR); error = vcache_get(vdp->v_mount, &foundino, sizeof(foundino), &tdp); if (error) return (error); *vpp = tdp; return (0); } if (dp->i_number == foundino) { vref(vdp); /* we want ourself, ie "." */ *vpp = vdp; } else { error = vcache_get(vdp->v_mount, &foundino, sizeof(foundino), &tdp); if (error) return (error); *vpp = tdp; } /* * Insert name into cache if appropriate. */ cache_enter(vdp, *vpp, cnp->cn_nameptr, cnp->cn_namelen, cnp->cn_flags); return 0; }
/* * Write a directory entry after a call to namei, using the parameters * that it left in nameidata. The argument ip is the inode which the new * directory entry will refer to. Dvp is a pointer to the directory to * be written, which was left locked by namei. Remaining parameters * (ulr_offset, ulr_count) indicate how the space for the new * entry is to be obtained. */ int ext2fs_direnter(struct inode *ip, struct vnode *dvp, const struct ufs_lookup_results *ulr, struct componentname *cnp) { struct ext2fs_direct *ep, *nep; struct inode *dp; struct buf *bp; struct ext2fs_direct newdir; struct iovec aiov; struct uio auio; u_int dsize; int error, loc, newentrysize, spacefree; char *dirbuf; struct ufsmount *ump = VFSTOUFS(dvp->v_mount); int dirblksiz = ump->um_dirblksiz; dp = VTOI(dvp); newdir.e2d_ino = h2fs32(ip->i_number); newdir.e2d_namlen = cnp->cn_namelen; if (ip->i_e2fs->e2fs.e2fs_rev > E2FS_REV0 && (ip->i_e2fs->e2fs.e2fs_features_incompat & EXT2F_INCOMPAT_FTYPE)) { newdir.e2d_type = inot2ext2dt(IFTODT(ip->i_e2fs_mode)); } else { newdir.e2d_type = 0; } memcpy(newdir.e2d_name, cnp->cn_nameptr, (unsigned)cnp->cn_namelen + 1); newentrysize = EXT2FS_DIRSIZ(cnp->cn_namelen); if (ulr->ulr_count == 0) { /* * If ulr_count is 0, then namei could find no * space in the directory. Here, ulr_offset will * be on a directory block boundary and we will write the * new entry into a fresh block. */ if (ulr->ulr_offset & (dirblksiz - 1)) panic("ext2fs_direnter: newblk"); auio.uio_offset = ulr->ulr_offset; newdir.e2d_reclen = h2fs16(dirblksiz); auio.uio_resid = newentrysize; aiov.iov_len = newentrysize; aiov.iov_base = (void *)&newdir; auio.uio_iov = &aiov; auio.uio_iovcnt = 1; auio.uio_rw = UIO_WRITE; UIO_SETUP_SYSSPACE(&auio); error = VOP_WRITE(dvp, &auio, IO_SYNC, cnp->cn_cred); if (dirblksiz > dvp->v_mount->mnt_stat.f_bsize) /* XXX should grow with balloc() */ panic("ext2fs_direnter: frag size"); else if (!error) { error = ext2fs_setsize(dp, roundup(ext2fs_size(dp), dirblksiz)); if (error) return (error); dp->i_flag |= IN_CHANGE; uvm_vnp_setsize(dvp, ext2fs_size(dp)); } return (error); } /* * If ulr_count is non-zero, then namei found space * for the new entry in the range ulr_offset to * ulr_offset + ulr_count in the directory. * To use this space, we may have to compact the entries located * there, by copying them together towards the beginning of the * block, leaving the free space in one usable chunk at the end. */ /* * Get the block containing the space for the new directory entry. */ if ((error = ext2fs_blkatoff(dvp, (off_t)ulr->ulr_offset, &dirbuf, &bp)) != 0) return (error); /* * Find space for the new entry. In the simple case, the entry at * offset base will have the space. If it does not, then namei * arranged that compacting the region ulr_offset to * ulr_offset + ulr_count would yield the * space. */ ep = (struct ext2fs_direct *)dirbuf; dsize = EXT2FS_DIRSIZ(ep->e2d_namlen); spacefree = fs2h16(ep->e2d_reclen) - dsize; for (loc = fs2h16(ep->e2d_reclen); loc < ulr->ulr_count; ) { nep = (struct ext2fs_direct *)(dirbuf + loc); if (ep->e2d_ino) { /* trim the existing slot */ ep->e2d_reclen = h2fs16(dsize); ep = (struct ext2fs_direct *)((char *)ep + dsize); } else { /* overwrite; nothing there; header is ours */ spacefree += dsize; } dsize = EXT2FS_DIRSIZ(nep->e2d_namlen); spacefree += fs2h16(nep->e2d_reclen) - dsize; loc += fs2h16(nep->e2d_reclen); memcpy((void *)ep, (void *)nep, dsize); } /* * Update the pointer fields in the previous entry (if any), * copy in the new entry, and write out the block. */ if (ep->e2d_ino == 0) { #ifdef DIAGNOSTIC if (spacefree + dsize < newentrysize) panic("ext2fs_direnter: compact1"); #endif newdir.e2d_reclen = h2fs16(spacefree + dsize); } else { #ifdef DIAGNOSTIC if (spacefree < newentrysize) { printf("ext2fs_direnter: compact2 %u %u", (u_int)spacefree, (u_int)newentrysize); panic("ext2fs_direnter: compact2"); } #endif newdir.e2d_reclen = h2fs16(spacefree); ep->e2d_reclen = h2fs16(dsize); ep = (struct ext2fs_direct *)((char *)ep + dsize); } memcpy((void *)ep, (void *)&newdir, (u_int)newentrysize); error = VOP_BWRITE(bp->b_vp, bp); dp->i_flag |= IN_CHANGE | IN_UPDATE; if (!error && ulr->ulr_endoff && ulr->ulr_endoff < ext2fs_size(dp)) error = ext2fs_truncate(dvp, (off_t)ulr->ulr_endoff, IO_SYNC, cnp->cn_cred); return (error); }
__compactcall void ext2fs_ls(struct open_file *f, const char *pattern) { struct file *fp = (struct file *)f->f_fsdata; size_t block_size = fp->f_fs->e2fs_bsize; char *buf; size_t buf_size; entry_t *names = 0, *n, **np; fp->f_seekp = 0; while (fp->f_seekp < (off_t)fp->f_di.e2di_size) { struct ext2fs_direct *dp, *edp; int rc = buf_read_file(f, &buf, &buf_size); if (rc) goto out; if (buf_size != block_size || buf_size == 0) goto out; dp = (struct ext2fs_direct *)buf; edp = (struct ext2fs_direct *)(buf + buf_size); for (; dp < edp; dp = (void *)((char *)dp + fs2h16(dp->e2d_reclen))) { const char *t; if (fs2h16(dp->e2d_reclen) <= 0) goto out; if (fs2h32(dp->e2d_ino) == 0) continue; if (dp->e2d_type >= NELEM(typestr) || !(t = typestr[dp->e2d_type])) { /* * This does not handle "old" * filesystems properly. On little * endian machines, we get a bogus * type name if the namlen matches a * valid type identifier. We could * check if we read namlen "0" and * handle this case specially, if * there were a pressing need... */ printf("bad dir entry\n"); goto out; } if (pattern && !fnmatch(dp->e2d_name, pattern)) continue; n = alloc(sizeof *n + strlen(dp->e2d_name)); if (!n) { printf("%d: %s (%s)\n", fs2h32(dp->e2d_ino), dp->e2d_name, t); continue; } n->e_ino = fs2h32(dp->e2d_ino); n->e_type = dp->e2d_type; strcpy(n->e_name, dp->e2d_name); for (np = &names; *np; np = &(*np)->e_next) { if (strcmp(n->e_name, (*np)->e_name) < 0) break; } n->e_next = *np; *np = n; } fp->f_seekp += buf_size; } if (names) { entry_t *p_names = names; do { n = p_names; printf("%d: %s (%s)\n", n->e_ino, n->e_name, typestr[n->e_type]); p_names = n->e_next; } while (p_names); } else { printf("not found\n"); } out: if (names) { do { n = names; names = n->e_next; dealloc(n, 0); } while (names); } return; }