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