The fsck_ffs program had previously only been able to expand the size

of its lost+found directory by allocating direct block pointers. The
effect was that it was limited to about 19,000 files. One of Peter Holm's
tests produced a filesystem with about 23,000 lost files which meant
that fsck_ffs was unable to recover it. This update allows lost+found
to be expanded into a single indirect block which allows it to store
up to about 6,573,000 lost files.

Reported by:  Peter Holm
Sponsored by: Netflix
This commit is contained in:
Kirk McKusick 2021-01-02 22:31:55 -08:00
parent 1868a91fac
commit 997f81af43

View File

@ -633,57 +633,114 @@ makeentry(ino_t parent, ino_t ino, const char *name)
static int static int
expanddir(union dinode *dp, char *name) expanddir(union dinode *dp, char *name)
{ {
ufs2_daddr_t lastbn, newblk; ufs2_daddr_t lastlbn, oldblk, newblk, indirblk;
struct bufarea *bp; size_t filesize, lastlbnsize;
struct bufarea *bp, *nbp;
struct inodesc idesc; struct inodesc idesc;
char *cp, firstblk[DIRBLKSIZ]; int indiralloced;
char *cp;
lastbn = lblkno(&sblock, DIP(dp, di_size)); nbp = NULL;
if (lastbn >= UFS_NDADDR - 1 || DIP(dp, di_db[lastbn]) == 0 || indiralloced = newblk = indirblk = 0;
DIP(dp, di_size) == 0) pwarn("NO SPACE LEFT IN %s", name);
if (!preen && reply("EXPAND") == 0)
return (0); return (0);
if ((newblk = allocblk(sblock.fs_frag)) == 0) filesize = DIP(dp, di_size);
return (0); lastlbn = lblkno(&sblock, filesize);
DIP_SET(dp, di_db[lastbn + 1], DIP(dp, di_db[lastbn])); /*
DIP_SET(dp, di_db[lastbn], newblk); * We only expand lost+found to a single indirect block.
DIP_SET(dp, di_size, DIP(dp, di_size) + sblock.fs_bsize); */
DIP_SET(dp, di_blocks, DIP(dp, di_blocks) + btodb(sblock.fs_bsize)); if ((DIP(dp, di_mode) & IFMT) != IFDIR || filesize == 0 ||
bp = getdirblk(DIP(dp, di_db[lastbn + 1]), lastlbn >= UFS_NDADDR + NINDIR(&sblock))
sblksize(&sblock, DIP(dp, di_size), lastbn + 1)); goto bad;
if (bp->b_errs) /*
* If last block is a fragment, expand it to a full size block.
*/
lastlbnsize = sblksize(&sblock, filesize, lastlbn);
if (lastlbnsize < sblock.fs_bsize) {
oldblk = DIP(dp, di_db[lastlbn]);
bp = getdirblk(oldblk, lastlbnsize);
if (bp->b_errs)
goto bad;
if ((newblk = allocblk(sblock.fs_frag)) == 0)
goto bad;
nbp = getdatablk(newblk, sblock.fs_bsize, BT_DIRDATA);
if (nbp->b_errs)
goto bad;
DIP_SET(dp, di_db[lastlbn], newblk);
DIP_SET(dp, di_size, filesize + sblock.fs_bsize - lastlbnsize);
DIP_SET(dp, di_blocks, DIP(dp, di_blocks) +
btodb(sblock.fs_bsize - lastlbnsize));
inodirty(dp);
memmove(nbp->b_un.b_buf, bp->b_un.b_buf, lastlbnsize);
memset(&nbp->b_un.b_buf[lastlbnsize], 0,
sblock.fs_bsize - lastlbnsize);
for (cp = &nbp->b_un.b_buf[lastlbnsize];
cp < &nbp->b_un.b_buf[sblock.fs_bsize];
cp += DIRBLKSIZ)
memmove(cp, &emptydir, sizeof emptydir);
dirty(nbp);
nbp->b_flags &= ~B_INUSE;
idesc.id_blkno = oldblk;
idesc.id_numfrags = numfrags(&sblock, lastlbnsize);
(void)freeblock(&idesc);
return (1);
}
if ((newblk = allocblk(sblock.fs_frag)) == 0)
goto bad; goto bad;
memmove(firstblk, bp->b_un.b_buf, DIRBLKSIZ);
bp = getdirblk(newblk, sblock.fs_bsize); bp = getdirblk(newblk, sblock.fs_bsize);
if (bp->b_errs) if (bp->b_errs)
goto bad; goto bad;
memmove(bp->b_un.b_buf, firstblk, DIRBLKSIZ); memset(bp->b_un.b_buf, 0, sblock.fs_bsize);
for (cp = &bp->b_un.b_buf[DIRBLKSIZ]; for (cp = bp->b_un.b_buf;
cp < &bp->b_un.b_buf[sblock.fs_bsize]; cp < &bp->b_un.b_buf[sblock.fs_bsize];
cp += DIRBLKSIZ) cp += DIRBLKSIZ)
memmove(cp, &emptydir, sizeof emptydir); memmove(cp, &emptydir, sizeof emptydir);
dirty(bp); dirty(bp);
bp = getdirblk(DIP(dp, di_db[lastbn + 1]), if (lastlbn < UFS_NDADDR) {
sblksize(&sblock, DIP(dp, di_size), lastbn + 1)); DIP_SET(dp, di_db[lastlbn], newblk);
if (bp->b_errs) } else {
goto bad; /*
memmove(bp->b_un.b_buf, &emptydir, sizeof emptydir); * Allocate indirect block if needed.
pwarn("NO SPACE LEFT IN %s", name); */
if ((indirblk = DIP(dp, di_ib[0])) == 0) {
if ((indirblk = allocblk(sblock.fs_frag)) == 0)
goto bad;
indiralloced = 1;
}
nbp = getdatablk(indirblk, sblock.fs_bsize, BT_LEVEL1);
if (nbp->b_errs)
goto bad;
if (indiralloced) {
memset(nbp->b_un.b_buf, 0, sblock.fs_bsize);
DIP_SET(dp, di_ib[0], indirblk);
DIP_SET(dp, di_blocks,
DIP(dp, di_blocks) + btodb(sblock.fs_bsize));
}
IBLK_SET(nbp, lastlbn - UFS_NDADDR, newblk);
dirty(nbp);
nbp->b_flags &= ~B_INUSE;
}
DIP_SET(dp, di_size, filesize + sblock.fs_bsize);
DIP_SET(dp, di_blocks, DIP(dp, di_blocks) + btodb(sblock.fs_bsize));
inodirty(dp);
if (preen) if (preen)
printf(" (EXPANDED)\n"); printf(" (EXPANDED)\n");
else if (reply("EXPAND") == 0)
goto bad;
dirty(bp);
inodirty(dp);
return (1); return (1);
bad: bad:
DIP_SET(dp, di_db[lastbn], DIP(dp, di_db[lastbn + 1])); pfatal(" (EXPANSION FAILED)\n");
DIP_SET(dp, di_db[lastbn + 1], 0); if (nbp != NULL)
DIP_SET(dp, di_size, DIP(dp, di_size) - sblock.fs_bsize); nbp->b_flags &= ~B_INUSE;
DIP_SET(dp, di_blocks, DIP(dp, di_blocks) - btodb(sblock.fs_bsize)); if (newblk != 0) {
/* Free the block we allocated above */ idesc.id_blkno = newblk;
idesc.id_blkno = newblk; idesc.id_numfrags = sblock.fs_frag;
idesc.id_numfrags = sblock.fs_frag; (void)freeblock(&idesc);
(void)freeblock(&idesc); }
if (indiralloced) {
idesc.id_blkno = indirblk;
idesc.id_numfrags = sblock.fs_frag;
(void)freeblock(&idesc);
}
return (0); return (0);
} }