From ac4b20a0a71e693d9b241a8396ec4540fc4cef68 Mon Sep 17 00:00:00 2001 From: Kirk McKusick Date: Mon, 25 Feb 2019 21:58:19 +0000 Subject: [PATCH] After a crash, a file that extends into indirect blocks may end up shorter than its size resulting in a hole as its final block (which is a violation of the invarients of the UFS filesystem). Soft updates will always ensure that the file size is correct when writing inodes to disk for files that contain only direct block pointers. However soft updates does not roll back sizes for files with indirect blocks that it has set to unallocated because their contents have not yet been written to disk. Hence, the file can appear to have a hole at its end because the block pointer has been rolled back to zero when its inode was written to disk. Thus, fsck_ffs calculates the last allocated block in the file. For files that extend into indirect blocks, fsck_ffs checks for a size past the last allocated block of the file and if that is found, shortens the file to reference the last allocated block thus avoiding having it reference a hole at its end. Submitted by: Chuck Silvers Tested by: Chuck Silvers MFC after: 1 week Sponsored by: Netflix --- sbin/fsck_ffs/fsck.h | 2 ++ sbin/fsck_ffs/globs.c | 2 ++ sbin/fsck_ffs/pass1.c | 44 +++++++++++++++++++++++++++++++++++++++++ sbin/fsck_ffs/setup.c | 1 + sbin/fsdb/fsdb.c | 27 +++++++++++++++++++++++++ sys/ufs/ffs/ffs_alloc.c | 22 +++++++++++++++++++++ sys/ufs/ffs/fs.h | 3 ++- 7 files changed, 100 insertions(+), 1 deletion(-) diff --git a/sbin/fsck_ffs/fsck.h b/sbin/fsck_ffs/fsck.h index ed7e8f129c35..39fae42249fe 100644 --- a/sbin/fsck_ffs/fsck.h +++ b/sbin/fsck_ffs/fsck.h @@ -232,6 +232,7 @@ struct inodesc { ufs_lbn_t id_lbn; /* logical block number of current block */ ufs2_daddr_t id_blkno; /* current block number being examined */ int id_numfrags; /* number of frags contained in block */ + ufs_lbn_t id_lballoc; /* pass1: last LBN that is allocated */ off_t id_filesize; /* for DATA nodes, the size of the directory */ ufs2_daddr_t id_entryno;/* for DATA nodes, current entry number */ int id_loc; /* for DATA nodes, current location in dir */ @@ -291,6 +292,7 @@ extern long countdirs; /* number of directories we actually found */ #define MIBSIZE 3 /* size of fsck sysctl MIBs */ extern int adjrefcnt[MIBSIZE]; /* MIB command to adjust inode reference cnt */ extern int adjblkcnt[MIBSIZE]; /* MIB command to adjust inode block count */ +extern int setsize[MIBSIZE]; /* MIB command to set inode size */ extern int adjndir[MIBSIZE]; /* MIB command to adjust number of directories */ extern int adjnbfree[MIBSIZE]; /* MIB command to adjust number of free blocks */ extern int adjnifree[MIBSIZE]; /* MIB command to adjust number of free inodes */ diff --git a/sbin/fsck_ffs/globs.c b/sbin/fsck_ffs/globs.c index e04a5194b976..f4575b2a86fa 100644 --- a/sbin/fsck_ffs/globs.c +++ b/sbin/fsck_ffs/globs.c @@ -63,6 +63,7 @@ unsigned long numdirs, listmax; long countdirs; /* number of directories we actually found */ int adjrefcnt[MIBSIZE]; /* MIB command to adjust inode reference cnt */ int adjblkcnt[MIBSIZE]; /* MIB command to adjust inode block count */ +int setsize[MIBSIZE]; /* MIB command to set inode size */ int adjndir[MIBSIZE]; /* MIB command to adjust number of directories */ int adjnbfree[MIBSIZE]; /* MIB command to adjust number of free blocks */ int adjnifree[MIBSIZE]; /* MIB command to adjust number of free inodes */ @@ -131,6 +132,7 @@ fsckinit(void) countdirs = 0; bzero(adjrefcnt, sizeof(int) * MIBSIZE); bzero(adjblkcnt, sizeof(int) * MIBSIZE); + bzero(setsize, sizeof(int) * MIBSIZE); bzero(adjndir, sizeof(int) * MIBSIZE); bzero(adjnbfree, sizeof(int) * MIBSIZE); bzero(adjnifree, sizeof(int) * MIBSIZE); diff --git a/sbin/fsck_ffs/pass1.c b/sbin/fsck_ffs/pass1.c index c9064ea704e4..2245ee3fa84e 100644 --- a/sbin/fsck_ffs/pass1.c +++ b/sbin/fsck_ffs/pass1.c @@ -247,6 +247,7 @@ checkinode(ino_t inumber, struct inodesc *idesc, int rebuildcg) off_t kernmaxfilesize; ufs2_daddr_t ndb; mode_t mode; + uintmax_t fixsize; int j, ret, offset; if ((dp = getnextinode(inumber, rebuildcg)) == NULL) @@ -377,6 +378,7 @@ checkinode(ino_t inumber, struct inodesc *idesc, int rebuildcg) idesc->id_type = SNAP; else idesc->id_type = ADDR; + idesc->id_lballoc = -1; (void)ckinode(dp, idesc); if (sblock.fs_magic == FS_UFS2_MAGIC && dp->dp2.di_extsize > 0) { idesc->id_type = ADDR; @@ -422,6 +424,46 @@ checkinode(ino_t inumber, struct inodesc *idesc, int rebuildcg) rwerror("ADJUST INODE BLOCK COUNT", cmd.value); } } + /* + * Soft updates will always ensure that the file size is correct + * for files that contain only direct block pointers. However + * soft updates does not roll back sizes for files with indirect + * blocks that it has set to unallocated because their contents + * have not yet been written to disk. Hence, the file can appear + * to have a hole at its end because the block pointer has been + * rolled back to zero. Thus, id_lballoc tracks the last allocated + * block in the file. Here, for files that extend into indirect + * blocks, we check for a size past the last allocated block of + * the file and if that is found, shorten the file to reference + * the last allocated block to avoid having it reference a hole + * at its end. + */ + if (DIP(dp, di_size) > UFS_NDADDR * sblock.fs_bsize && + idesc->id_lballoc != lblkno(&sblock, DIP(dp, di_size) - 1)) { + fixsize = lblktosize(&sblock, idesc->id_lballoc + 1); + pwarn("INODE %lu: FILE SIZE %ju BEYOND END OF ALLOCATED FILE, " + "SIZE SHOULD BE %ju", (u_long)inumber, + (uintmax_t)DIP(dp, di_size), fixsize); + if (preen) + printf(" (ADJUSTED)\n"); + else if (reply("ADJUST") == 0) + return (1); + if (bkgrdflag == 0) { + dp = ginode(inumber); + DIP_SET(dp, di_size, fixsize); + inodirty(dp); + } else { + cmd.value = idesc->id_number; + cmd.size = fixsize; + if (debug) + printf("setsize ino %ju size set to %ju\n", + (uintmax_t)cmd.value, (uintmax_t)cmd.size); + if (sysctl(setsize, MIBSIZE, 0, 0, + &cmd, sizeof cmd) == -1) + rwerror("SET INODE SIZE", cmd.value); + } + + } return (1); unknown: pfatal("UNKNOWN FILE TYPE I=%lu", (u_long)inumber); @@ -523,5 +565,7 @@ pass1check(struct inodesc *idesc) */ idesc->id_entryno++; } + if (idesc->id_lballoc == -1 || idesc->id_lballoc < idesc->id_lbn) + idesc->id_lballoc = idesc->id_lbn; return (res); } diff --git a/sbin/fsck_ffs/setup.c b/sbin/fsck_ffs/setup.c index 8599f6cde739..98783a820beb 100644 --- a/sbin/fsck_ffs/setup.c +++ b/sbin/fsck_ffs/setup.c @@ -140,6 +140,7 @@ setup(char *dev) size = MIBSIZE; if (sysctlnametomib("vfs.ffs.adjrefcnt", adjrefcnt, &size) < 0|| sysctlnametomib("vfs.ffs.adjblkcnt", adjblkcnt, &size) < 0|| + sysctlnametomib("vfs.ffs.setsize", setsize, &size) < 0 || sysctlnametomib("vfs.ffs.freefiles", freefiles, &size) < 0|| sysctlnametomib("vfs.ffs.freedirs", freedirs, &size) < 0 || sysctlnametomib("vfs.ffs.freeblks", freeblks, &size) < 0) { diff --git a/sbin/fsdb/fsdb.c b/sbin/fsdb/fsdb.c index c3553a687066..74b3a82a8c8b 100644 --- a/sbin/fsdb/fsdb.c +++ b/sbin/fsdb/fsdb.c @@ -157,6 +157,7 @@ CMDFUNC(chctime); /* Change ctime */ CMDFUNC(chatime); /* Change atime */ CMDFUNC(chinum); /* Change inode # of dirent */ CMDFUNC(chname); /* Change dirname of dirent */ +CMDFUNC(chsize); /* Change size */ struct cmdtable cmds[] = { { "help", "Print out help", 1, 1, FL_RO, helpfn }, @@ -186,6 +187,7 @@ struct cmdtable cmds[] = { { "chgrp", "Change group of current inode to GROUP", 2, 2, FL_WR, chgroup }, { "chflags", "Change flags of current inode to FLAGS", 2, 2, FL_WR, chaflags }, { "chgen", "Change generation number of current inode to GEN", 2, 2, FL_WR, chgen }, + { "chsize", "Change size of current inode to SIZE", 2, 2, FL_WR, chsize }, { "btime", "Change btime of current inode to BTIME", 2, 2, FL_WR, chbtime }, { "mtime", "Change mtime of current inode to MTIME", 2, 2, FL_WR, chmtime }, { "ctime", "Change ctime of current inode to CTIME", 2, 2, FL_WR, chctime }, @@ -1017,6 +1019,31 @@ CMDFUNCSTART(chgen) return rval; } +CMDFUNCSTART(chsize) +{ + int rval = 1; + off_t size; + char *cp; + + if (!checkactive()) + return 1; + + size = strtoll(argv[1], &cp, 0); + if (cp == argv[1] || *cp != '\0') { + warnx("bad size `%s'", argv[1]); + return 1; + } + + if (size < 0) { + warnx("size set to negative (%jd)\n", (intmax_t)size); + return(1); + } + DIP_SET(curinode, di_size, size); + inodirty(curinode); + printactive(0); + return rval; +} + CMDFUNCSTART(linkcount) { int rval = 1; diff --git a/sys/ufs/ffs/ffs_alloc.c b/sys/ufs/ffs/ffs_alloc.c index e70da0e746ab..f4bdd5789ab5 100644 --- a/sys/ufs/ffs/ffs_alloc.c +++ b/sys/ufs/ffs/ffs_alloc.c @@ -3037,6 +3037,8 @@ ffs_fserr(fs, inum, cp) * the count to zero will cause the inode to be freed. * adjblkcnt(inode, amt) - adjust the number of blocks used by the * inode by the specified amount. + * adjsize(inode, size) - set the size of the inode to the + * specified size. * adjndir, adjbfree, adjifree, adjffree, adjnumclusters(amt) - * adjust the superblock summary. * freedirs(inode, count) - directory inodes [inode..inode + count - 1] @@ -3078,6 +3080,9 @@ SYSCTL_PROC(_vfs_ffs, FFS_ADJ_REFCNT, adjrefcnt, CTLFLAG_WR|CTLTYPE_STRUCT, static SYSCTL_NODE(_vfs_ffs, FFS_ADJ_BLKCNT, adjblkcnt, CTLFLAG_WR, sysctl_ffs_fsck, "Adjust Inode Used Blocks Count"); +static SYSCTL_NODE(_vfs_ffs, FFS_SET_SIZE, setsize, CTLFLAG_WR, + sysctl_ffs_fsck, "Set the inode size"); + static SYSCTL_NODE(_vfs_ffs, FFS_ADJ_NDIR, adjndir, CTLFLAG_WR, sysctl_ffs_fsck, "Adjust number of directories"); @@ -3230,6 +3235,23 @@ sysctl_ffs_fsck(SYSCTL_HANDLER_ARGS) vput(vp); break; + case FFS_SET_SIZE: +#ifdef DEBUG + if (fsckcmds) { + printf("%s: set inode %jd size to %jd\n", + mp->mnt_stat.f_mntonname, (intmax_t)cmd.value, + (intmax_t)cmd.size); + } +#endif /* DEBUG */ + if ((error = ffs_vget(mp, (ino_t)cmd.value, LK_EXCLUSIVE, &vp))) + break; + ip = VTOI(vp); + DIP_SET(ip, i_size, cmd.size); + ip->i_flag |= IN_CHANGE | IN_MODIFIED; + error = ffs_update(vp, 1); + vput(vp); + break; + case FFS_DIR_FREE: filetype = IFDIR; /* fall through */ diff --git a/sys/ufs/ffs/fs.h b/sys/ufs/ffs/fs.h index 0560bc693afa..d052b603d5f1 100644 --- a/sys/ufs/ffs/fs.h +++ b/sys/ufs/ffs/fs.h @@ -221,7 +221,8 @@ #define FFS_UNLINK 14 /* remove a name in the filesystem */ #define FFS_SET_INODE 15 /* update an on-disk inode */ #define FFS_SET_BUFOUTPUT 16 /* set buffered writing on descriptor */ -#define FFS_MAXID 16 /* number of valid ffs ids */ +#define FFS_SET_SIZE 17 /* set inode size */ +#define FFS_MAXID 17 /* number of valid ffs ids */ /* * Command structure passed in to the filesystem to adjust filesystem values.