#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/int_types.h>
#include <sys/mntent.h>
#include <sys/fs/ufs_fs.h>
#include <sys/vnode.h>
#include <sys/fs/ufs_inode.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/mnttab.h>
#include <signal.h>
#include <string.h>
#include <sys/vfstab.h>
#include <sys/statvfs.h>
#include <sys/filio.h>
#include <ustat.h>
#include <errno.h>
#include "fsck.h"
static void usage(void) __NORETURN;
static long argtol(int, char *, char *, int);
static void checkfilesys(char *);
static void check_sanity(char *);
static void report_limbo(const void *, VISIT, int);
#define QUICK_CHECK 'm'
#define ALL_no 'n'
#define ALL_NO 'N'
#define UFS_OPTS 'o'
#define ECHO_CMD 'V'
#define ALL_yes 'y'
#define ALL_YES 'Y'
#define VERBOSE 'v'
static char *subopts[] = {
#define PREEN 0
"p",
#define BLOCK 1
"b",
#define DEBUG 2
"d",
#define ONLY_WRITES 3
"w",
#define FORCE 4
"f",
NULL
};
char *magic_fs[] = {
"",
"/",
"/usr",
NULL
};
daddr32_t bflag;
daddr32_t n_blks;
daddr32_t maxfsblock;
int debug;
int errorlocked;
int exitstat;
int fflag;
int fsmodified;
int fswritefd;
int iscorrupt;
int islog;
int islogok;
int interrupted;
int mflag;
int mountfd;
int overflowed_lf;
int rflag;
int reattached_dir;
int broke_dir_link;
int verbose;
char hotroot;
char mountedfs;
char nflag;
char preen;
char rerun;
char *blockmap;
char *devname;
char yflag;
short *lncntp;
ushort_t *statemap;
fsck_ino_t maxino;
fsck_ino_t countdirs;
fsck_ino_t n_files;
void *limbo_dirs;
int
main(int argc, char *argv[])
{
int c;
int wflag = 0;
char *suboptions, *value;
struct rlimit rlimit;
extern int optind;
extern char *optarg;
while ((c = getopt(argc, argv, "mnNo:VvyY")) != EOF) {
switch (c) {
case QUICK_CHECK:
mflag++;
break;
case ALL_no:
case ALL_NO:
nflag++;
yflag = 0;
break;
case VERBOSE:
verbose++;
break;
case UFS_OPTS:
if (optarg == NULL) {
usage();
}
suboptions = optarg;
while (*suboptions != '\0') {
switch (getsubopt(&suboptions, subopts,
&value)) {
case PREEN:
preen++;
break;
case BLOCK:
bflag = argtol(BLOCK, "block",
value, 10);
(void) printf("Alternate super block "
"location: %ld.\n",
(long)bflag);
break;
case DEBUG:
debug++;
verbose++;
break;
case ONLY_WRITES:
wflag++;
break;
case FORCE:
fflag++;
break;
default:
usage();
}
}
break;
case ECHO_CMD:
{
int opt_count;
char *opt_text;
(void) printf("fsck -F ufs ");
for (opt_count = 1; opt_count < argc;
opt_count++) {
opt_text = argv[opt_count];
if (opt_text)
(void) printf("%s ", opt_text);
}
(void) printf("\n");
}
break;
case ALL_yes:
case ALL_YES:
yflag++;
nflag = 0;
break;
default:
usage();
}
}
argc -= optind;
argv += optind;
if (argc == 0)
usage();
rflag++;
if (signal(SIGINT, SIG_IGN) != SIG_IGN)
(void) signal(SIGINT, catch);
if (preen)
(void) signal(SIGQUIT, catchquit);
if (getrlimit(RLIMIT_DATA, &rlimit) == 0) {
rlimit.rlim_cur = rlimit.rlim_max;
(void) setrlimit(RLIMIT_DATA, &rlimit);
}
while (argc-- > 0) {
if (wflag && !writable(*argv)) {
(void) fprintf(stderr, "not writeable '%s'\n", *argv);
argv++;
if (exitstat == 0)
exitstat = EXBADPARM;
} else {
checkfilesys(*argv++);
}
}
if (interrupted)
exitstat = EXSIGNAL;
exit(exitstat);
}
static long
argtol(int flag, char *req, char *str, int base)
{
char *cp = str;
long ret = -1;
errno = 0;
if (str != NULL)
ret = strtol(str, &cp, base);
if (cp == str || *cp) {
(void) fprintf(stderr, "-%c flag requires a %s\n", flag, req);
exit(EXBADPARM);
}
if (errno != 0) {
(void) fprintf(stderr, "-%c %s value out of range\n",
flag, req);
}
return (ret);
}
static void
checkfilesys(char *filesys)
{
daddr32_t n_ffree, n_bfree;
char *devstr;
fsck_ino_t files;
daddr32_t blks;
fsck_ino_t inumber;
int zlinks_printed;
fsck_ino_t limbo_victim;
double dbl_nffree, dbl_dsize;
int quiet_dups;
mountfd = -1;
hotroot = 0;
mountedfs = M_NOMNT;
reattached_dir = 0;
broke_dir_link = 0;
iscorrupt = 1;
islog = 0;
islogok = 0;
overflowed_lf = 0;
errorlocked = is_errorlocked(filesys);
limbo_dirs = NULL;
if ((devstr = setup(filesys)) == NULL) {
if (!iscorrupt) {
return;
}
if (preen)
pfatal("CAN'T CHECK FILE SYSTEM.");
if (exitstat == 0)
exitstat = mflag ? EXUMNTCHK : EXERRFATAL;
exit(exitstat);
} else {
devname = devstr;
}
if (mflag) {
check_sanity(filesys);
}
if (debug)
printclean();
iscorrupt = 0;
if (!preen) {
if (mountedfs != M_NOMNT)
(void) printf("** Currently Mounted on %s\n",
sblock.fs_fsmnt);
else
(void) printf("** Last Mounted on %s\n",
sblock.fs_fsmnt);
(void) printf("** Phase 1 - Check Blocks and Sizes\n");
}
pass1();
if (have_dups()) {
if (preen)
pfatal("INTERNAL ERROR: dups with -o p");
(void) printf("** Phase 1b - Rescan For More DUPS\n");
pass1b();
}
if (!preen)
(void) printf("** Phase 2 - Check Pathnames\n");
pass2();
if (!preen)
(void) printf("** Phase 3a - Check Connectivity\n");
pass3a();
if (!preen)
(void) printf("** Phase 3b - Verify Shadows/ACLs\n");
pass3b();
if (!preen)
(void) printf("** Phase 4 - Check Reference Counts\n");
pass4();
if (!preen)
(void) printf("** Phase 5 - Check Cylinder Groups\n");
recount:
pass5();
if (overflowed_lf) {
iscorrupt = 1;
}
if (!nflag && mountedfs == M_RW) {
(void) printf("FILESYSTEM MAY STILL BE INCONSISTENT.\n");
rerun = 1;
}
if (have_dups()) {
quiet_dups = (reply("LIST REMAINING DUPS") == 0);
if (report_dups(quiet_dups) > 0)
iscorrupt = 1;
(void) printf("WARNING: DATA LOSS MAY HAVE OCCURRED DUE TO "
"DUP BLOCKS.\nVERIFY FILE CONTENTS BEFORE USING.\n");
}
if (limbo_dirs != NULL) {
pwarn("Orphan directories not cleared or reconnected:\n");
twalk(limbo_dirs, report_limbo);
while (limbo_dirs != NULL) {
limbo_victim = *(fsck_ino_t *)limbo_dirs;
if (limbo_victim != 0) {
(void) tdelete((void *)limbo_victim,
&limbo_dirs,
ino_t_cmp);
}
}
rerun = 1;
}
if (iscorrupt) {
if (mountedfs == M_RW)
(void) printf("FS IS MOUNTED R/W AND"
" FSCK DID ITS BEST TO FIX"
" INCONSISTENCIES.\n");
else
(void) printf("FILESYSTEM MAY STILL BE"
" INCONSISTENT.\n");
rerun = 1;
}
if (updateclean()) {
if (!preen)
(void) printf(
"Log was discarded, updating cyl groups\n");
goto recount;
}
if (debug)
printclean();
ckfini();
n_ffree = sblock.fs_cstotal.cs_nffree;
n_bfree = sblock.fs_cstotal.cs_nbfree;
files = maxino - UFSROOTINO - 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 (debug && (files > 0 || blks > 0)) {
countdirs = sblock.fs_cstotal.cs_ndir - countdirs;
pwarn("Reclaimed: %d directories, %d files, %lld fragments\n",
countdirs, files - countdirs,
(longlong_t)blks);
}
dbl_nffree = (double)n_ffree;
dbl_dsize = (double)sblock.fs_dsize;
if (!verbose) {
pwarn("%ld files, %lld used, %lld free "
"(%lld frags, %lld blocks, %.1f%% fragmentation)\n",
(long)n_files, (longlong_t)n_blks,
(longlong_t)n_ffree + sblock.fs_frag * n_bfree,
(longlong_t)n_ffree, (longlong_t)n_bfree,
(dbl_nffree * 100.0) / dbl_dsize);
} else {
pwarn("\nFilesystem summary:\n");
pwarn("Inodes in use: %ld\n", (long)n_files);
pwarn("Blocks in use: %lld\n", (longlong_t)n_blks);
pwarn("Total free fragments: %lld\n",
(longlong_t)n_ffree + sblock.fs_frag * n_bfree);
pwarn("Free fragments not in blocks: %lld\n",
(longlong_t)n_ffree);
pwarn("Total free blocks: %lld\n", (longlong_t)n_bfree);
pwarn("Fragment/block fragmentation: %.1f%%\n",
(dbl_nffree * 100.0) / dbl_dsize);
pwarn("");
if (files < 0)
pwarn("%d inodes missing\n", -files);
if (blks < 0)
pwarn("%lld blocks missing\n", -(longlong_t)blks);
zlinks_printed = 0;
for (inumber = UFSROOTINO; inumber < maxino; inumber++) {
if (S_IS_ZLINK(statemap[inumber])) {
if (zlinks_printed == 0) {
pwarn("The following zero "
"link count inodes remain:");
}
if (zlinks_printed) {
if ((zlinks_printed % 9) == 0)
(void) puts(",\n");
else
(void) puts(", ");
}
(void) printf("%u", inumber);
zlinks_printed++;
}
}
if ((zlinks_printed != 0) && ((zlinks_printed % 9) != 0))
(void) putchar('\n');
}
free_dup_state();
inocleanup();
free(blockmap);
free(statemap);
free((void *)lncntp);
lncntp = NULL;
blockmap = NULL;
statemap = NULL;
if (iscorrupt && exitstat == 0)
exitstat = EXFNDERRS;
if (fsmodified)
(void) printf("\n***** FILE SYSTEM WAS MODIFIED *****\n");
if (overflowed_lf)
(void) printf("\n***** %s FULL, MUST REMOVE ENTRIES *****\n",
lfname);
if (reattached_dir) {
(void) printf("ORPHANED DIRECTORIES REATTACHED; DIR LINK "
"COUNTS MAY NOT BE CORRECT.\n");
rerun = 1;
}
if (broke_dir_link) {
(void) printf(
"DIRECTORY HARDLINK BROKEN; LOOPS MAY STILL EXIST.\n");
rerun = 1;
}
if (iscorrupt)
(void) printf("***** FILE SYSTEM IS BAD *****\n");
if (rerun) {
if (mountedfs == M_RW)
(void) printf("\n***** PLEASE RERUN FSCK ON UNMOUNTED"
" FILE SYSTEM *****\n");
else
(void) printf("\n***** PLEASE RERUN FSCK *****\n");
}
if ((exitstat == 0) &&
(((mountedfs != M_NOMNT) && !errorlocked) || hotroot)) {
exitstat = EXROOTOKAY;
}
if ((exitstat == 0) && rerun)
exitstat = EXFNDERRS;
if (mountedfs != M_NOMNT) {
if (!fsmodified)
return;
fswritefd = open(devstr, O_RDWR, 0);
if (fswritefd != -1) {
(void) ioctl(fswritefd, _FIOFFS, NULL);
(void) close(fswritefd);
}
if (!preen)
(void) printf("\n***** REBOOT NOW *****\n");
exitstat = EXREBOOTNOW;
}
}
void
check_sanity(char *filename)
{
struct stat64 stbd, stbr;
char *devname;
struct ustat usb;
char vfsfilename[MAXPATHLEN];
struct vfstab vfsbuf;
FILE *vfstab;
struct statvfs vfs_stat;
int found_magic[MAGIC_LIMIT];
int magic_cnt;
int is_magic = 0;
int is_block = 0;
int is_file = 0;
(void) memset((void *)found_magic, 0, sizeof (found_magic));
if (stat64(filename, &stbd) < 0) {
(void) fprintf(stderr,
"ufs fsck: sanity check failed : cannot stat %s\n", filename);
exit(EXNOSTAT);
}
if (S_ISBLK(stbd.st_mode)) {
is_block = 1;
} else if (S_ISCHR(stbd.st_mode)) {
is_block = 0;
} else if (S_ISREG(stbd.st_mode)) {
is_file = 1;
}
if (!is_file && (vfstab = fopen(VFSTAB, "r")) != NULL) {
for (magic_cnt = 0; magic_cnt < MAGIC_LIMIT; magic_cnt++) {
if (magic_cnt == MAGIC_NONE)
continue;
if (getvfsfile(vfstab, &vfsbuf,
magic_fs[magic_cnt]) == 0) {
if (is_block)
devname = vfsbuf.vfs_special;
else
devname = vfsbuf.vfs_fsckdev;
if (stat64(devname, &stbr) == 0) {
if (stbr.st_rdev == stbd.st_rdev) {
found_magic[magic_cnt] = 1;
is_magic = magic_cnt;
break;
}
}
}
}
}
if (!is_magic && (ustat(stbd.st_rdev, &usb) == 0)) {
(void) fprintf(stderr,
"ufs fsck: sanity check: %s already mounted\n", filename);
exit(EXMOUNTED);
}
if (is_magic) {
(void) strcpy(vfsfilename, magic_fs[is_magic]);
if (statvfs(vfsfilename, &vfs_stat) != 0) {
(void) fprintf(stderr, "ufs fsck: Cannot stat %s\n",
vfsfilename);
exit(EXNOSTAT);
}
if (!(vfs_stat.f_flag & ST_RDONLY)) {
(void) fprintf(stderr,
"ufs fsck: sanity check:"
"%s already mounted read/write\n", filename);
exit(EXMOUNTED);
}
}
if (islog && !islogok) {
(void) fprintf(stderr,
"ufs fsck: sanity check: %s needs checking\n", filename);
exit(EXUMNTCHK);
}
if ((sblock.fs_state + (long)sblock.fs_time == FSOKAY) &&
(sblock.fs_clean == FSCLEAN || sblock.fs_clean == FSSTABLE ||
(sblock.fs_clean == FSLOG && islog))) {
(void) fprintf(stderr,
"ufs fsck: sanity check: %s okay\n", filename);
} else {
(void) fprintf(stderr,
"ufs fsck: sanity check: %s needs checking\n", filename);
exit(EXUMNTCHK);
}
exit(EXOKAY);
}
caddr_t
hasvfsopt(struct vfstab *vfs, char *opt)
{
struct mnttab mtab;
if (vfs->vfs_mntopts == NULL)
return (NULL);
mtab.mnt_mntopts = vfs->vfs_mntopts;
return (hasmntopt(&mtab, opt));
}
static void __NORETURN
usage(void)
{
(void) fprintf(stderr,
"ufs usage: fsck [-F ufs] [-m] [-n] [-V] [-v] [-y] "
"[-o p,b=#,w,f] [special ....]\n");
exit(EXBADPARM);
}
static void
report_limbo(const void *node, VISIT order, int level)
{
fsck_ino_t ino = *(fsck_ino_t *)node;
if ((order == postorder) || (order == leaf)) {
(void) printf(" Inode %d\n", ino);
}
}