#define _WANT_P_OSREL
#include <sys/param.h>
#include <sys/file.h>
#include <sys/mount.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include <sys/uio.h>
#include <sys/disklabel.h>
#include <ufs/ufs/dinode.h>
#include <ufs/ffs/fs.h>
#include <err.h>
#include <errno.h>
#include <fstab.h>
#include <grp.h>
#include <inttypes.h>
#include <mntopts.h>
#include <paths.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
#include "fsck.h"
static int restarts;
static char snapname[BUFSIZ];
static void usage(void) __dead2;
static intmax_t argtoimax(int flag, const char *req, const char *str, int base);
static int checkfilesys(char *filesys);
static int setup_bkgrdchk(struct statfs *mntp, int sbrdfailed, char **filesys);
int
main(int argc, char *argv[])
{
int ch;
struct rlimit rlimit;
struct itimerval itimerval;
int fsret;
int ret = 0;
sync();
skipclean = 1;
inoopt = 0;
while ((ch = getopt(argc, argv, "b:Bc:CdEfFm:npRrSyZz")) != -1) {
switch (ch) {
case 'b':
skipclean = 0;
bflag = argtoimax('b', "number", optarg, 10);
printf("Alternate super block location: %jd\n", bflag);
break;
case 'B':
bkgrdflag = 1;
break;
case 'c':
skipclean = 0;
cvtlevel = argtoimax('c', "conversion level", optarg,
10);
if (cvtlevel < 3)
errx(EEXIT, "cannot do level %d conversion",
cvtlevel);
break;
case 'd':
debug++;
break;
case 'E':
Eflag++;
break;
case 'f':
skipclean = 0;
break;
case 'F':
bkgrdcheck = 1;
break;
case 'm':
lfmode = argtoimax('m', "mode", optarg, 8);
if (lfmode &~ 07777)
errx(EEXIT, "bad mode to -m: %o", lfmode);
printf("** lost+found creation mode %o\n", lfmode);
break;
case 'n':
nflag++;
yflag = 0;
break;
case 'p':
preen++;
case 'C':
ckclean++;
break;
case 'R':
wantrestart = 1;
break;
case 'r':
inoopt++;
break;
case 'S':
surrender = 1;
break;
case 'y':
yflag++;
nflag = 0;
break;
case 'Z':
Zflag++;
break;
case 'z':
zflag++;
break;
default:
usage();
}
}
argc -= optind;
argv += optind;
if (!argc)
usage();
if (bkgrdflag && cvtlevel > 0) {
pfatal("CANNOT CONVERT A SNAPSHOT\n");
exit(EEXIT);
}
if (signal(SIGINT, SIG_IGN) != SIG_IGN)
(void)signal(SIGINT, catch);
if (ckclean)
(void)signal(SIGQUIT, catchquit);
signal(SIGINFO, infohandler);
if (bkgrdflag) {
signal(SIGALRM, alarmhandler);
itimerval.it_interval.tv_sec = 5;
itimerval.it_interval.tv_usec = 0;
itimerval.it_value.tv_sec = 5;
itimerval.it_value.tv_usec = 0;
setitimer(ITIMER_REAL, &itimerval, NULL);
}
if (getrlimit(RLIMIT_DATA, &rlimit) == 0) {
rlimit.rlim_cur = rlimit.rlim_max;
(void)setrlimit(RLIMIT_DATA, &rlimit);
}
while (argc > 0) {
if ((fsret = checkfilesys(*argv)) == ERESTART)
continue;
ret |= fsret;
argc--;
argv++;
}
if (returntosingle)
ret = 2;
exit(ret);
}
static intmax_t
argtoimax(int flag, const char *req, const char *str, int base)
{
char *cp;
intmax_t ret;
ret = strtoimax(str, &cp, base);
if (cp == str || *cp)
errx(EEXIT, "-%c flag requires a %s", flag, req);
return (ret);
}
static int
checkfilesys(char *filesys)
{
ufs2_daddr_t n_ffree, n_bfree;
struct dups *dp;
struct statfs *mntp;
intmax_t blks, files;
size_t size;
int sbreadfailed, ofsmodified;
fsutilinit();
fsckinit();
cdevname = filesys;
if (debug && ckclean)
pwarn("starting\n");
mntp = getmntpoint(filesys);
if (mntp != NULL)
filesys = mntp->f_mntfromname;
else
filesys = blockcheck(filesys);
sblock_init();
sbreadfailed = 0;
if (openfilesys(filesys) == 0 || readsb() == 0)
sbreadfailed = 1;
if (bkgrdcheck) {
if (sbreadfailed)
exit(3);
if ((sblock.fs_flags & FS_NEEDSFSCK) == FS_NEEDSFSCK)
exit(4);
if ((sblock.fs_flags & FS_SUJ) == FS_SUJ) {
maxino = sblock.fs_ncg * sblock.fs_ipg;
maxfsblock = sblock.fs_size;
bufinit();
preen = 1;
if (suj_check(filesys) == 0)
exit(4);
}
if ((sblock.fs_flags & FS_DOSOFTDEP) == 0)
exit(5);
size = MIBSIZE;
if (sysctlnametomib("vfs.ffs.adjrefcnt", adjrefcnt, &size) < 0)
exit(6);
if ((mntp == NULL && sblock.fs_clean == 1) ||
(mntp != NULL && (sblock.fs_flags & FS_UNCLEAN) == 0))
exit(7);
exit(0);
}
if (ckclean && skipclean) {
if (sbreadfailed)
exit(3);
if (bkgrdflag == 0 &&
(nflag || (fswritefd = open(filesys, O_WRONLY)) < 0)) {
fswritefd = -1;
if (preen)
pfatal("NO WRITE ACCESS");
printf(" (NO WRITE)");
}
if ((sblock.fs_flags & FS_GJOURNAL) != 0) {
if (sblock.fs_clean == 1) {
pwarn("FILE SYSTEM CLEAN; SKIPPING CHECKS\n");
exit(0);
}
if ((sblock.fs_flags &
(FS_UNCLEAN | FS_NEEDSFSCK)) == 0) {
bufinit();
gjournal_check(filesys);
if (chkdoreload(mntp, pwarn) == 0)
exit(0);
exit(4);
} else {
pfatal("FULL FSCK NEEDED, CANNOT RUN FAST "
"FSCK\n");
}
}
close(fswritefd);
fswritefd = -1;
}
if (bkgrdflag) {
switch (setup_bkgrdchk(mntp, sbreadfailed, &filesys)) {
case -1:
goto clean;
case 0:
exit(EEXIT);
case 1:
preen = 1;
break;
}
}
switch (setup(filesys)) {
case 0:
if (preen)
pfatal("CAN'T CHECK FILE SYSTEM.");
return (EEXIT);
case -1:
clean:
pwarn("clean, %ld free ", (long)(sblock.fs_cstotal.cs_nffree +
sblock.fs_frag * sblock.fs_cstotal.cs_nbfree));
printf("(%jd frags, %jd blocks, %.1f%% fragmentation)\n",
(intmax_t)sblock.fs_cstotal.cs_nffree,
(intmax_t)sblock.fs_cstotal.cs_nbfree,
sblock.fs_cstotal.cs_nffree * 100.0 / sblock.fs_dsize);
return (0);
}
if (bkgrdflag == 0 && (sblock.fs_flags & FS_SUJ) == FS_SUJ) {
if ((sblock.fs_flags & FS_NEEDSFSCK) != FS_NEEDSFSCK &&
skipclean) {
sujrecovery = 1;
if (suj_check(filesys) == 0) {
pwarn("\n**** FILE SYSTEM MARKED CLEAN ****\n");
if (chkdoreload(mntp, pwarn) == 0)
exit(0);
exit(4);
}
sujrecovery = 0;
pwarn("Skipping journal, "
"falling through to full fsck\n");
}
if (fswritefd != -1) {
sblock.fs_mtime = time(NULL);
sbdirty();
ofsmodified = fsmodified;
flush(fswritefd, &sblk);
fsmodified = ofsmodified;
}
}
if ((sblock.fs_flags & FS_METACKHASH) == 0)
sblock.fs_metackhash = 0;
ckhashadd = 0;
if (preen == 0 && yflag == 0 && sblock.fs_magic != FS_UFS1_MAGIC &&
fswritefd != -1 && getosreldate() >= P_OSREL_CK_CYLGRP) {
if ((sblock.fs_metackhash & CK_CYLGRP) == 0 &&
reply("ADD CYLINDER GROUP CHECK-HASH PROTECTION") != 0) {
ckhashadd |= CK_CYLGRP;
sblock.fs_metackhash |= CK_CYLGRP;
}
if ((sblock.fs_metackhash & CK_SUPERBLOCK) == 0 &&
getosreldate() >= P_OSREL_CK_SUPERBLOCK &&
reply("ADD SUPERBLOCK CHECK-HASH PROTECTION") != 0) {
ckhashadd |= CK_SUPERBLOCK;
sblock.fs_metackhash |= CK_SUPERBLOCK;
}
if ((sblock.fs_metackhash & CK_INODE) == 0 &&
getosreldate() >= P_OSREL_CK_INODE &&
reply("ADD INODE CHECK-HASH PROTECTION") != 0) {
ckhashadd |= CK_INODE;
sblock.fs_metackhash |= CK_INODE;
}
#ifdef notyet
if ((sblock.fs_metackhash & CK_INDIR) == 0 &&
getosreldate() >= P_OSREL_CK_INDIR &&
reply("ADD INDIRECT BLOCK CHECK-HASH PROTECTION") != 0) {
ckhashadd |= CK_INDIR;
sblock.fs_metackhash |= CK_INDIR;
}
if ((sblock.fs_metackhash & CK_DIR) == 0 &&
getosreldate() >= P_OSREL_CK_DIR &&
reply("ADD DIRECTORY CHECK-HASH PROTECTION") != 0) {
ckhashadd |= CK_DIR;
sblock.fs_metackhash |= CK_DIR;
}
#endif
if (ckhashadd != 0) {
sblock.fs_flags |= FS_METACKHASH;
sbdirty();
}
}
resolved = 1;
if (preen == 0 || debug) {
printf("** Last Mounted on %s\n", sblock.fs_fsmnt);
if (mntp != NULL && mntp->f_flags & MNT_ROOTFS)
printf("** Root file system\n");
printf("** Phase 1 - Check Blocks and Sizes\n");
}
clock_gettime(CLOCK_REALTIME_PRECISE, &startprog);
pass1();
IOstats("Pass1");
if (duplist) {
if (preen || usedsoftdep)
pfatal("INTERNAL ERROR: DUPS WITH %s%s%s",
preen ? "-p" : "",
(preen && usedsoftdep) ? " AND " : "",
usedsoftdep ? "SOFTUPDATES" : "");
if (preen == 0 || debug)
printf("** Phase 1b - Rescan For More DUPS\n");
pass1b();
IOstats("Pass1b");
}
if (preen == 0 || debug)
printf("** Phase 2 - Check Pathnames\n");
pass2();
IOstats("Pass2");
if (preen == 0 || debug)
printf("** Phase 3 - Check Connectivity\n");
pass3();
IOstats("Pass3");
if (preen == 0 || debug)
printf("** Phase 4 - Check Reference Counts\n");
pass4();
IOstats("Pass4");
if (preen == 0 || debug)
printf("** Phase 5 - Check Cyl groups\n");
snapflush(std_checkblkavail);
if (cgheader_corrupt) {
printf("PHASE 5 SKIPPED DUE TO CORRUPT CYLINDER GROUP "
"HEADER(S)\n\n");
} else {
pass5();
IOstats("Pass5");
}
n_ffree = sblock.fs_cstotal.cs_nffree;
n_bfree = sblock.fs_cstotal.cs_nbfree;
files = maxino - UFS_ROOTINO - sblock.fs_cstotal.cs_nifree - n_files;
blks = n_blks +
sblock.fs_ncg * (cgdmin(&sblock, 0) - cgsblock(&sblock, 0));
blks += cgsblock(&sblock, 0) - cgbase(&sblock, 0);
blks += howmany(sblock.fs_cssize, sblock.fs_fsize);
blks = maxfsblock - (n_ffree + sblock.fs_frag * n_bfree) - blks;
if (bkgrdflag && (files > 0 || blks > 0)) {
countdirs = sblock.fs_cstotal.cs_ndir - countdirs;
pwarn("Reclaimed: %ld directories, %jd files, %jd fragments\n",
countdirs, files - countdirs, blks);
}
pwarn("%ld files, %jd used, %ju free ",
(long)n_files, (intmax_t)n_blks,
(uintmax_t)n_ffree + sblock.fs_frag * n_bfree);
printf("(%ju frags, %ju blocks, %.1f%% fragmentation)\n",
(uintmax_t)n_ffree, (uintmax_t)n_bfree,
n_ffree * 100.0 / sblock.fs_dsize);
if (debug) {
if (files < 0)
printf("%jd inodes missing\n", -files);
if (blks < 0)
printf("%jd blocks missing\n", -blks);
if (duplist != NULL) {
printf("The following duplicate blocks remain:");
for (dp = duplist; dp; dp = dp->next)
printf(" %jd,", (intmax_t)dp->dup);
printf("\n");
}
}
duplist = (struct dups *)0;
muldup = (struct dups *)0;
inocleanup();
if (fsmodified) {
sblock.fs_time = time(NULL);
sbdirty();
}
if (cvtlevel && (sblk.b_flags & B_DIRTY) != 0) {
if (sbput(fswritefd, &sblock, sblock.fs_ncg) == 0)
fsmodified = 1;
}
if (rerun)
resolved = 0;
if (bkgrdflag == 0 && mntp != NULL && (mntp->f_flags & MNT_RDONLY) == 0)
resolved = 0;
ckfini(resolved);
if (fsmodified && !preen)
printf("\n***** FILE SYSTEM WAS MODIFIED *****\n");
if (rerun) {
if (wantrestart && (restarts++ < 10) &&
(preen || reply("RESTART")))
return (ERESTART);
printf("\n***** PLEASE RERUN FSCK *****\n");
}
if (chkdoreload(mntp, pwarn) != 0) {
if (!fsmodified)
return (0);
if (!preen)
printf("\n***** REBOOT NOW *****\n");
sync();
return (4);
}
return (rerun ? ERERUN : 0);
}
static int
setup_bkgrdchk(struct statfs *mntp, int sbreadfailed, char **filesys)
{
struct stat snapdir;
struct group *grp;
struct iovec *iov;
char errmsg[255];
int iovlen;
size_t size;
if (mntp == NULL) {
pwarn("NOT MOUNTED, CANNOT RUN IN BACKGROUND\n");
return (0);
}
if ((mntp->f_flags & MNT_RDONLY) != 0) {
pwarn("MOUNTED READ-ONLY, CANNOT RUN IN BACKGROUND\n");
return (0);
}
if ((mntp->f_flags & MNT_SOFTDEP) == 0) {
pwarn("NOT USING SOFT UPDATES, CANNOT RUN IN BACKGROUND\n");
return (0);
}
if (sbreadfailed) {
pwarn("SUPERBLOCK READ FAILED, CANNOT RUN IN BACKGROUND\n");
return (0);
}
if ((sblock.fs_flags & FS_NEEDSFSCK) != 0) {
pwarn("FULL FSCK NEEDED, CANNOT RUN IN BACKGROUND\n");
return (0);
}
if (skipclean && ckclean &&
(sblock.fs_flags & (FS_UNCLEAN|FS_NEEDSFSCK)) == 0) {
pwarn("FILE SYSTEM CLEAN; SKIPPING CHECKS\n");
return (-1);
}
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) {
pwarn("KERNEL LACKS BACKGROUND FSCK SUPPORT\n");
return (0);
}
bkgrdsumadj = 1;
if (sysctlnametomib("vfs.ffs.adjndir", adjndir, &size) < 0 ||
sysctlnametomib("vfs.ffs.adjnbfree", adjnbfree, &size) < 0 ||
sysctlnametomib("vfs.ffs.adjnifree", adjnifree, &size) < 0 ||
sysctlnametomib("vfs.ffs.adjnffree", adjnffree, &size) < 0 ||
sysctlnametomib("vfs.ffs.adjnumclusters", adjnumclusters,
&size) < 0) {
bkgrdsumadj = 0;
pwarn("KERNEL LACKS RUNTIME SUPERBLOCK SUMMARY ADJUSTMENT "
"SUPPORT\n");
}
snprintf(snapname, sizeof snapname, "%s/.snap", mntp->f_mntonname);
if (stat(snapname, &snapdir) < 0) {
if (errno != ENOENT) {
pwarn("CANNOT FIND SNAPSHOT DIRECTORY %s: %s, CANNOT "
"RUN IN BACKGROUND\n", snapname, strerror(errno));
return (0);
}
if ((grp = getgrnam("operator")) == NULL ||
mkdir(snapname, 0770) < 0 ||
chown(snapname, -1, grp->gr_gid) < 0 ||
chmod(snapname, 0770) < 0) {
pwarn("CANNOT CREATE SNAPSHOT DIRECTORY %s: %s, "
"CANNOT RUN IN BACKGROUND\n", snapname,
strerror(errno));
return (0);
}
} else if (!S_ISDIR(snapdir.st_mode)) {
pwarn("%s IS NOT A DIRECTORY, CANNOT RUN IN BACKGROUND\n",
snapname);
return (0);
}
iov = NULL;
iovlen = 0;
errmsg[0] = '\0';
snprintf(snapname, sizeof snapname, "%s/.snap/fsck_snapshot",
mntp->f_mntonname);
build_iovec(&iov, &iovlen, "fstype", "ffs", 4);
build_iovec(&iov, &iovlen, "from", snapname, (size_t)-1);
build_iovec(&iov, &iovlen, "fspath", mntp->f_mntonname, (size_t)-1);
build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg));
build_iovec(&iov, &iovlen, "update", NULL, 0);
build_iovec(&iov, &iovlen, "snapshot", NULL, 0);
while (nmount(iov, iovlen, mntp->f_flags) < 0) {
if (errno == EEXIST && unlink(snapname) == 0)
continue;
pwarn("CANNOT CREATE SNAPSHOT %s: %s %s\n", snapname,
strerror(errno), errmsg);
return (0);
}
if (openfilesys(snapname) == 0) {
unlink(snapname);
pwarn("CANNOT OPEN SNAPSHOT %s: %s, CANNOT RUN IN "
"BACKGROUND\n", snapname, strerror(errno));
return (0);
}
unlink(snapname);
free(sblock.fs_csp);
free(sblock.fs_si);
if (readsb() == 0) {
pwarn("CANNOT READ SNAPSHOT SUPERBLOCK\n");
return (0);
}
*filesys = snapname;
cmd.version = FFS_CMD_VERSION;
cmd.handle = fsreadfd;
return (1);
}
static void
usage(void)
{
(void) fprintf(stderr,
"usage: %s [-BCdEFfnpRrSyZ] [-b block] [-c level] [-m mode] filesystem ...\n",
getprogname());
exit(1);
}
void
infohandler(int sig __unused)
{
got_siginfo = 1;
}
void
alarmhandler(int sig __unused)
{
got_sigalarm = 1;
}