diff options
Diffstat (limited to 'disk-utils/fsck.minix.c')
-rw-r--r-- | disk-utils/fsck.minix.c | 1438 |
1 files changed, 1438 insertions, 0 deletions
diff --git a/disk-utils/fsck.minix.c b/disk-utils/fsck.minix.c new file mode 100644 index 0000000..b978684 --- /dev/null +++ b/disk-utils/fsck.minix.c @@ -0,0 +1,1438 @@ +/* + * fsck.minix.c - a file system consistency checker for Linux. + * + * (C) 1991, 1992 Linus Torvalds. This file may be redistributed + * as per the GNU copyleft. + */ + +/* + * 09.11.91 - made the first rudimentary functions + * + * 10.11.91 - updated, does checking, no repairs yet. + * Sent out to the mailing-list for testing. + * + * 14.11.91 - Testing seems to have gone well. Added some + * correction-code, and changed some functions. + * + * 15.11.91 - More correction code. Hopefully it notices most + * cases now, and tries to do something about them. + * + * 16.11.91 - More corrections (thanks to Mika Jalava). Most + * things seem to work now. Yeah, sure. + * + * + * 19.04.92 - Had to start over again from this old version, as a + * kernel bug ate my enhanced fsck in February. + * + * 28.02.93 - added support for different directory entry sizes.. + * + * Sat Mar 6 18:59:42 1993, faith@cs.unc.edu: Output namelen with + * super-block information + * + * Sat Oct 9 11:17:11 1993, faith@cs.unc.edu: make exit status conform + * to that required by fsutil + * + * Mon Jan 3 11:06:52 1994 - Dr. Wettstein (greg%wind.uucp@plains.nodak.edu) + * Added support for file system valid flag. Also + * added program_version variable and output of + * program name and version number when program + * is executed. + * + * 30.10.94 - added support for v2 filesystem + * (Andreas Schwab, schwab@issan.informatik.uni-dortmund.de) + * + * 10.12.94 - added test to prevent checking of mounted fs adapted + * from Theodore Ts'o's (tytso@athena.mit.edu) e2fsck + * program. (Daniel Quinlan, quinlan@yggdrasil.com) + * + * 01.07.96 - Fixed the v2 fs stuff to use the right #defines and such + * for modern libcs (janl@math.uio.no, Nicolai Langfeldt) + * + * 02.07.96 - Added C bit fiddling routines from rmk@ecs.soton.ac.uk + * (Russell King). He made them for ARM. It would seem + * that the ARM is powerful enough to do this in C whereas + * i386 and m64k must use assembly to get it fast >:-) + * This should make minix fsck systemindependent. + * (janl@math.uio.no, Nicolai Langfeldt) + * + * 04.11.96 - Added minor fixes from Andreas Schwab to avoid compiler + * warnings. Added mc68k bitops from + * Joerg Dorchain <dorchain@mpi-sb.mpg.de>. + * + * 06.11.96 - Added v2 code submitted by Joerg Dorchain, but written by + * Andreas Schwab. + * + * 1999-02-22 Arkadiusz MiĆkiewicz <misiek@pld.ORG.PL> + * - added Native Language Support + * + * 2008-04-06 James Youngman <jay@gnu.org> + * - Issue better error message if we fail to open the device. + * - Restore terminal state if we get a fatal signal. + * + * + * I've had no time to add comments - hopefully the function names + * are comments enough. As with all file system checkers, this assumes + * the file system is quiescent - don't use it on a mounted device + * unless you can be sure nobody is writing to it (and remember that the + * kernel can write to it when it searches for files). + * + */ + +#include <stdio.h> +#include <stdarg.h> +#include <errno.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <ctype.h> +#include <stdlib.h> +#include <termios.h> +#include <sys/stat.h> +#include <signal.h> +#include <getopt.h> + +#include "c.h" +#include "exitcodes.h" +#include "minix_programs.h" +#include "nls.h" +#include "pathnames.h" +#include "bitops.h" +#include "ismounted.h" +#include "all-io.h" +#include "closestream.h" +#include "rpmatch.h" +#include "strutils.h" + +#define ROOT_INO 1 +#define YESNO_LENGTH 64 + +/* Global variables used in minix_programs.h inline functions */ +int fs_version = 1; +char *super_block_buffer; + +static char *inode_buffer; + +#define Inode (((struct minix_inode *) inode_buffer) - 1) +#define Inode2 (((struct minix2_inode *) inode_buffer) - 1) + +static char *device_name; +static int device_fd; +static int repair, automatic, verbose, list, show, warn_mode, force; +static int directory, regular, blockdev, chardev, links, symlinks, total; + +static int changed; /* flags if the filesystem has been changed */ +static int errors_uncorrected; /* flag if some error was not corrected */ +static size_t dirsize = 16; +static size_t namelen = 14; +static struct termios termios; +static volatile sig_atomic_t termios_set; + +/* File-name data */ +#define MAX_DEPTH 50 +static int name_depth; +static char name_list[MAX_DEPTH][MINIX_NAME_MAX + 1]; + +/* Copy of the previous, just for error reporting - see get_current_name. This + * is a waste of 12kB or so. */ +static char current_name[MAX_DEPTH * (MINIX_NAME_MAX + 1) + 1]; + +static unsigned char *inode_count = NULL; +static unsigned char *zone_count = NULL; + +static void recursive_check(unsigned int ino); +static void recursive_check2(unsigned int ino); + +static char *inode_map; +static char *zone_map; + +#define inode_in_use(x) (isset(inode_map,(x)) != 0) +#define zone_in_use(x) (isset(zone_map,(x)-get_first_zone()+1) != 0) + +#define mark_inode(x) (setbit(inode_map,(x)),changed=1) +#define unmark_inode(x) (clrbit(inode_map,(x)),changed=1) + +#define mark_zone(x) (setbit(zone_map,(x)-get_first_zone()+1),changed=1) +#define unmark_zone(x) (clrbit(zone_map,(x)-get_first_zone()+1),changed=1) + +static void +reset(void) { + if (termios_set) + tcsetattr(STDIN_FILENO, TCSANOW, &termios); +} + +static void +fatalsig(int sig) { + /* We received a fatal signal. Reset the terminal. Also reset the + * signal handler and re-send the signal, so that the parent process + * knows which signal actually caused our death. */ + signal(sig, SIG_DFL); + reset(); + raise(sig); +} + +static void __attribute__((__noreturn__)) +leave(int status) { + reset(); + exit(status); +} + +static void __attribute__((__noreturn__)) +usage(void) { + FILE *out = stdout; + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options] <device>\n"), program_invocation_short_name); + fputs(USAGE_SEPARATOR, out); + fputs(_("Check the consistency of a Minix filesystem.\n"), out); + fputs(USAGE_OPTIONS, out); + fputs(_(" -l, --list list all filenames\n"), out); + fputs(_(" -a, --auto automatic repair\n"), out); + fputs(_(" -r, --repair interactive repair\n"), out); + fputs(_(" -v, --verbose be verbose\n"), out); + fputs(_(" -s, --super output super-block information\n"), out); + fputs(_(" -m, --uncleared activate mode not cleared warnings\n"), out); + fputs(_(" -f, --force force check\n"), out); + fputs(USAGE_SEPARATOR, out); + printf(USAGE_HELP_OPTIONS(18)); + printf(USAGE_MAN_TAIL("fsck.minix(8)")); + exit(FSCK_EX_OK); +} + +static void die(const char *fmt, ...) + __attribute__ ((__format__(__printf__, 1, 2))); + +static void +die(const char *fmt, ...) { + va_list ap; + + fprintf(stderr, UTIL_LINUX_VERSION); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fputc('\n', stderr); + leave(FSCK_EX_ERROR); +} + +/* This simply goes through the file-name data and prints out the current file. */ +static void +get_current_name(void) { + int i = 0, ct; + char *p, *q; + + q = current_name; + while (i < name_depth) { + p = name_list[i++]; + ct = namelen; + *q++ = '/'; + while (ct-- && *p) + *q++ = *p++; + } + if (i == 0) + *q++ = '/'; + *q = 0; +} + +static int +ask(const char *string, int def) { + int resp; + char input[YESNO_LENGTH]; + + if (!repair) { + printf("\n"); + errors_uncorrected = 1; + return 0; + } + if (automatic) { + printf("\n"); + if (!def) + errors_uncorrected = 1; + return def; + } + /* TRANSLATORS: these yes no questions uses rpmatch(), and should be + * translated. */ + printf(def ? _("%s (y/n)? ") : _("%s (n/y)? "), string); + fflush(stdout); + ignore_result( fgets(input, YESNO_LENGTH, stdin) ); + resp = rpmatch(input); + switch (resp) { + case RPMATCH_INVALID: + /* def = def */ + break; + case RPMATCH_NO: + case RPMATCH_YES: + def = resp; + break; + default: + /* rpmatch bug? */ + abort(); + } + if (def) + printf(_("y\n")); + else { + printf(_("n\n")); + errors_uncorrected = 1; + } + return def; +} + +/* Make certain that we aren't checking a filesystem that is on a mounted + * partition. Code adapted from e2fsck, Copyright (C) 1993, 1994 Theodore + * Ts'o. Also licensed under GPL. */ +static void +check_mount(void) { + int cont; + + if (!is_mounted(device_name)) + return; + + printf(_("%s is mounted. "), device_name); + if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) + cont = ask(_("Do you really want to continue"), 0); + else + cont = 0; + if (!cont) { + printf(_("check aborted.\n")); + exit(FSCK_EX_OK); + } + return; +} + + +static int is_valid_zone_nr(unsigned short nr) +{ + if (nr < get_first_zone()) + return 0; + else if (nr >= get_nzones()) + return 0; + return 1; +} + +/* check_zone_nr checks to see that *nr is a valid zone nr. If it isn't, it + * will possibly be repaired. Check_zone_nr sets *corrected if an error was + * corrected, and returns the zone (0 for no zone or a bad zone-number). */ +static int +check_zone_nr(unsigned short *nr, int *corrected) { + if (!*nr) + return 0; + + if (*nr < get_first_zone()) { + get_current_name(); + printf(_("Zone nr < FIRSTZONE in file `%s'."), current_name); + } else if (*nr >= get_nzones()) { + get_current_name(); + printf(_("Zone nr >= ZONES in file `%s'."), current_name); + } else + return *nr; + + if (ask(_("Remove block"), 1)) { + *nr = 0; + *corrected = 1; + } + return 0; +} + +static int +check_zone_nr2(unsigned int *nr, int *corrected) { + if (!*nr) + return 0; + + if (*nr < get_first_zone()) { + get_current_name(); + printf(_("Zone nr < FIRSTZONE in file `%s'."), current_name); + } else if (*nr >= get_nzones()) { + get_current_name(); + printf(_("Zone nr >= ZONES in file `%s'."), current_name); + } else + return *nr; + + if (ask(_("Remove block"), 1)) { + *nr = 0; + *corrected = 1; + } + return 0; +} + +/* read-block reads block nr into the buffer at addr. */ +static void +read_block(unsigned int nr, char *addr) { + if (!nr) { + memset(addr, 0, MINIX_BLOCK_SIZE); + return; + } + if (MINIX_BLOCK_SIZE * nr != lseek(device_fd, MINIX_BLOCK_SIZE * nr, SEEK_SET)) { + get_current_name(); + printf(_("Read error: unable to seek to block in file '%s'\n"), + current_name); + memset(addr, 0, MINIX_BLOCK_SIZE); + errors_uncorrected = 1; + } else if (MINIX_BLOCK_SIZE != read(device_fd, addr, MINIX_BLOCK_SIZE)) { + get_current_name(); + printf(_("Read error: bad block in file '%s'\n"), current_name); + memset(addr, 0, MINIX_BLOCK_SIZE); + errors_uncorrected = 1; + } +} + +/* write_block writes block nr to disk. */ +static void +write_block(unsigned int nr, char *addr) { + if (!nr) + return; + if (nr < get_first_zone() || nr >= get_nzones()) { + printf(_("Internal error: trying to write bad block\n" + "Write request ignored\n")); + errors_uncorrected = 1; + return; + } + if (MINIX_BLOCK_SIZE * nr != lseek(device_fd, MINIX_BLOCK_SIZE * nr, SEEK_SET)) + die(_("seek failed in write_block")); + if (MINIX_BLOCK_SIZE != write(device_fd, addr, MINIX_BLOCK_SIZE)) { + get_current_name(); + printf(_("Write error: bad block in file '%s'\n"), + current_name); + errors_uncorrected = 1; + } +} + +/* map-block calculates the absolute block nr of a block in a file. It sets + * 'changed' if the inode has needed changing, and re-writes any indirect + * blocks with errors. */ +static int +map_block(struct minix_inode *inode, unsigned int blknr) { + unsigned short ind[MINIX_BLOCK_SIZE >> 1]; + unsigned short dind[MINIX_BLOCK_SIZE >> 1]; + int blk_chg, block, result; + size_t range; + + if (blknr < 7) + return check_zone_nr(inode->i_zone + blknr, &changed); + blknr -= 7; + if (blknr < 512) { + block = check_zone_nr(inode->i_zone + 7, &changed); + read_block(block, (char *)ind); + blk_chg = 0; + result = check_zone_nr(blknr + ind, &blk_chg); + if (blk_chg) + write_block(block, (char *)ind); + return result; + } + blknr -= 512; + block = check_zone_nr(inode->i_zone + 8, &changed); + read_block(block, (char *)dind); + blk_chg = 0; + range = blknr / 512; + if (ARRAY_SIZE(dind) <= range) { + printf(_("Warning: block out of range\n")); + return 1; + } + result = check_zone_nr(dind + range, &blk_chg); + if (blk_chg) + write_block(block, (char *)dind); + block = result; + read_block(block, (char *)ind); + blk_chg = 0; + result = check_zone_nr(ind + (blknr % 512), &blk_chg); + if (blk_chg) + write_block(block, (char *)ind); + return result; +} + +static int +map_block2(struct minix2_inode *inode, unsigned int blknr) { + unsigned int ind[MINIX_BLOCK_SIZE >> 2]; + unsigned int dind[MINIX_BLOCK_SIZE >> 2]; + unsigned int tind[MINIX_BLOCK_SIZE >> 2]; + int blk_chg, block, result; + + if (blknr < 7) + return check_zone_nr2(inode->i_zone + blknr, &changed); + blknr -= 7; + if (blknr < 256) { + block = check_zone_nr2(inode->i_zone + 7, &changed); + read_block(block, (char *)ind); + blk_chg = 0; + result = check_zone_nr2(blknr + ind, &blk_chg); + if (blk_chg) + write_block(block, (char *)ind); + return result; + } + blknr -= 256; + if (blknr < 256 * 256) { + block = check_zone_nr2(inode->i_zone + 8, &changed); + read_block(block, (char *)dind); + blk_chg = 0; + result = check_zone_nr2(dind + blknr / 256, &blk_chg); + if (blk_chg) + write_block(block, (char *)dind); + block = result; + read_block(block, (char *)ind); + blk_chg = 0; + result = check_zone_nr2(ind + blknr % 256, &blk_chg); + if (blk_chg) + write_block(block, (char *)ind); + return result; + } + blknr -= 256 * 256; + block = check_zone_nr2(inode->i_zone + 9, &changed); + read_block(block, (char *)tind); + blk_chg = 0; + result = check_zone_nr2(tind + blknr / (256 * 256), &blk_chg); + if (blk_chg) + write_block(block, (char *)tind); + block = result; + read_block(block, (char *)dind); + blk_chg = 0; + result = check_zone_nr2(dind + (blknr / 256) % 256, &blk_chg); + if (blk_chg) + write_block(block, (char *)dind); + block = result; + read_block(block, (char *)ind); + blk_chg = 0; + result = check_zone_nr2(ind + blknr % 256, &blk_chg); + if (blk_chg) + write_block(block, (char *)ind); + return result; +} + +static void +write_super_block(void) { + /* v3 super block does not track state */ + if (fs_version == 3) + return; + /* Set the state of the filesystem based on whether or not there are + * uncorrected errors. The filesystem valid flag is unconditionally + * set if we get this far. */ + Super.s_state |= MINIX_VALID_FS; + if (errors_uncorrected) + Super.s_state |= MINIX_ERROR_FS; + else + Super.s_state &= ~MINIX_ERROR_FS; + + if (MINIX_BLOCK_SIZE != lseek(device_fd, MINIX_BLOCK_SIZE, SEEK_SET)) + die(_("seek failed in write_super_block")); + if (MINIX_BLOCK_SIZE != write(device_fd, super_block_buffer, MINIX_BLOCK_SIZE)) + die(_("unable to write super-block")); + return; +} + +static void +write_tables(void) { + unsigned long buffsz = get_inode_buffer_size(); + unsigned long imaps = get_nimaps(); + unsigned long zmaps = get_nzmaps(); + + write_super_block(); + + if (write_all(device_fd, inode_map, imaps * MINIX_BLOCK_SIZE)) + die(_("Unable to write inode map")); + + if (write_all(device_fd, zone_map, zmaps * MINIX_BLOCK_SIZE)) + die(_("Unable to write zone map")); + + if (write_all(device_fd, inode_buffer, buffsz)) + die(_("Unable to write inodes")); +} + +static void +get_dirsize(void) { + int block; + char blk[MINIX_BLOCK_SIZE]; + size_t size; + + if (fs_version == 2 || fs_version == 3) + block = Inode2[ROOT_INO].i_zone[0]; + else + block = Inode[ROOT_INO].i_zone[0]; + read_block(block, blk); + + for (size = 16; size < MINIX_BLOCK_SIZE; size <<= 1) { + if (strcmp(blk + size + 2, "..") == 0) { + dirsize = size; + namelen = size - 2; + return; + } + } + /* use defaults */ +} + +static void +read_superblock(void) { + if (MINIX_BLOCK_SIZE != lseek(device_fd, MINIX_BLOCK_SIZE, SEEK_SET)) + die(_("seek failed")); + + super_block_buffer = calloc(1, MINIX_BLOCK_SIZE); + if (!super_block_buffer) + die(_("unable to alloc buffer for superblock")); + + if (MINIX_BLOCK_SIZE != read(device_fd, super_block_buffer, MINIX_BLOCK_SIZE)) + die(_("unable to read super block")); + if (Super.s_magic == MINIX_SUPER_MAGIC) { + namelen = 14; + dirsize = 16; + fs_version = 1; + } else if (Super.s_magic == MINIX_SUPER_MAGIC2) { + namelen = 30; + dirsize = 32; + fs_version = 1; + } else if (Super.s_magic == MINIX2_SUPER_MAGIC) { + namelen = 14; + dirsize = 16; + fs_version = 2; + } else if (Super.s_magic == MINIX2_SUPER_MAGIC2) { + namelen = 30; + dirsize = 32; + fs_version = 2; + } else if (Super3.s_magic == MINIX3_SUPER_MAGIC) { + namelen = 60; + dirsize = 64; + fs_version = 3; + } else + die(_("bad magic number in super-block")); + if (get_zone_size() != 0 || MINIX_BLOCK_SIZE != 1024) + die(_("Only 1k blocks/zones supported")); + if (get_ninodes() == 0 || get_ninodes() == UINT32_MAX) + die(_("bad s_ninodes field in super-block")); + if (get_nimaps() * MINIX_BLOCK_SIZE * 8 < get_ninodes() + 1) + die(_("bad s_imap_blocks field in super-block")); + if (get_first_zone() > (off_t) get_nzones()) + die(_("bad s_firstdatazone field in super-block")); + if (get_nzmaps() * MINIX_BLOCK_SIZE * 8 < + get_nzones() - get_first_zone() + 1) + die(_("bad s_zmap_blocks field in super-block")); +} + +static void +read_tables(void) { + unsigned long inodes = get_ninodes(); + size_t buffsz = get_inode_buffer_size(); + off_t norm_first_zone = first_zone_data(); + off_t first_zone = get_first_zone(); + unsigned long zones = get_nzones(); + unsigned long imaps = get_nimaps(); + unsigned long zmaps = get_nzmaps(); + ssize_t rc; + + inode_map = malloc(imaps * MINIX_BLOCK_SIZE); + if (!inode_map) + die(_("Unable to allocate buffer for inode map")); + zone_map = malloc(zmaps * MINIX_BLOCK_SIZE); + if (!zone_map) + die(_("Unable to allocate buffer for zone map")); + inode_buffer = malloc(buffsz); + if (!inode_buffer) + die(_("Unable to allocate buffer for inodes")); + inode_count = calloc(1, inodes + 1); + if (!inode_count) + die(_("Unable to allocate buffer for inode count")); + zone_count = calloc(1, zones); + if (!zone_count) + die(_("Unable to allocate buffer for zone count")); + + rc = read(device_fd, inode_map, imaps * MINIX_BLOCK_SIZE); + if (rc < 0 || imaps * MINIX_BLOCK_SIZE != (size_t) rc) + die(_("Unable to read inode map")); + + rc = read(device_fd, zone_map, zmaps * MINIX_BLOCK_SIZE); + if (rc < 0 || zmaps * MINIX_BLOCK_SIZE != (size_t) rc) + die(_("Unable to read zone map")); + + rc = read(device_fd, inode_buffer, buffsz); + if (rc < 0 || buffsz != (size_t) rc) + die(_("Unable to read inodes")); + if (norm_first_zone != first_zone) { + printf(_("Warning: Firstzone != Norm_firstzone\n")); + errors_uncorrected = 1; + } + get_dirsize(); + if (show) { + printf(_("%ld inodes\n"), inodes); + printf(_("%ld blocks\n"), zones); + printf(_("Firstdatazone=%jd (%jd)\n"), + (intmax_t)first_zone, (intmax_t)norm_first_zone); + printf(_("Zonesize=%d\n"), MINIX_BLOCK_SIZE << get_zone_size()); + printf(_("Maxsize=%zu\n"), get_max_size()); + if (fs_version < 3) + printf(_("Filesystem state=%d\n"), Super.s_state); + printf(_("namelen=%zd\n\n"), namelen); + } +} + +static struct minix_inode * +get_inode(unsigned int nr) { + struct minix_inode *inode; + + if (!nr || nr > get_ninodes()) + return NULL; + total++; + inode = Inode + nr; + if (!inode_count[nr]) { + if (!inode_in_use(nr)) { + get_current_name(); + printf(_("Inode %d marked unused, " + "but used for file '%s'\n"), nr, current_name); + if (repair) { + if (ask(_("Mark in use"), 1)) + mark_inode(nr); + } else { + errors_uncorrected = 1; + } + } + if (S_ISDIR(inode->i_mode)) + directory++; + else if (S_ISREG(inode->i_mode)) + regular++; + else if (S_ISCHR(inode->i_mode)) + chardev++; + else if (S_ISBLK(inode->i_mode)) + blockdev++; + else if (S_ISLNK(inode->i_mode)) + symlinks++; + else if (S_ISSOCK(inode->i_mode)) + ; + else if (S_ISFIFO(inode->i_mode)) + ; + else { + get_current_name(); + printf(_("The file `%s' has mode %05o\n"), + current_name, inode->i_mode); + } + + } else + links++; + if (!++inode_count[nr]) { + printf(_("Warning: inode count too big.\n")); + inode_count[nr]--; + errors_uncorrected = 1; + } + return inode; +} + +static struct minix2_inode * +get_inode2(unsigned int nr) { + struct minix2_inode *inode; + + if (!nr || nr > get_ninodes()) + return NULL; + total++; + inode = Inode2 + nr; + if (!inode_count[nr]) { + if (!inode_in_use(nr)) { + get_current_name(); + printf(_("Inode %d marked unused, " + "but used for file '%s'\n"), nr, current_name); + if (repair) { + if (ask(_("Mark in use"), 1)) + mark_inode(nr); + else + errors_uncorrected = 1; + } + } + if (S_ISDIR(inode->i_mode)) + directory++; + else if (S_ISREG(inode->i_mode)) + regular++; + else if (S_ISCHR(inode->i_mode)) + chardev++; + else if (S_ISBLK(inode->i_mode)) + blockdev++; + else if (S_ISLNK(inode->i_mode)) + symlinks++; + else if (S_ISSOCK(inode->i_mode)) ; + else if (S_ISFIFO(inode->i_mode)) ; + else { + get_current_name(); + printf(_("The file `%s' has mode %05o\n"), + current_name, inode->i_mode); + } + } else + links++; + if (!++inode_count[nr]) { + printf(_("Warning: inode count too big.\n")); + inode_count[nr]--; + errors_uncorrected = 1; + } + return inode; +} + +static void +check_root(void) { + struct minix_inode *inode = Inode + ROOT_INO; + + if (!inode || !S_ISDIR(inode->i_mode)) + die(_("root inode isn't a directory")); +} + +static void +check_root2(void) { + struct minix2_inode *inode = Inode2 + ROOT_INO; + + if (!inode || !S_ISDIR(inode->i_mode)) + die(_("root inode isn't a directory")); +} + +static int +add_zone(unsigned short *znr, int *corrected) { + int block; + + block = check_zone_nr(znr, corrected); + if (!block) + return 0; + if (zone_count[block]) { + get_current_name(); + printf(_("Block has been used before. Now in file `%s'."), + current_name); + if (ask(_("Clear"), 1)) { + *znr = 0; + block = 0; + *corrected = 1; + } + } + if (!block) + return 0; + if (!zone_in_use(block)) { + get_current_name(); + printf(_("Block %d in file `%s' is marked not in use."), + block, current_name); + if (ask(_("Correct"), 1)) + mark_zone(block); + } + if (!++zone_count[block]) + zone_count[block]--; + return block; +} + +static int +add_zone2(unsigned int *znr, int *corrected) { + int block; + + block = check_zone_nr2(znr, corrected); + if (!block) + return 0; + if (zone_count[block]) { + get_current_name(); + printf(_("Block has been used before. Now in file `%s'."), + current_name); + if (ask(_("Clear"), 1)) { + *znr = 0; + block = 0; + *corrected = 1; + } + } + if (!block) + return 0; + if (!zone_in_use(block)) { + get_current_name(); + printf(_("Block %d in file `%s' is marked not in use."), + block, current_name); + if (ask(_("Correct"), 1)) + mark_zone(block); + } + if (!++zone_count[block]) + zone_count[block]--; + return block; +} + +static void +add_zone_ind(unsigned short *znr, int *corrected) { + static char blk[MINIX_BLOCK_SIZE]; + int i, chg_blk = 0; + int block; + + block = add_zone(znr, corrected); + if (!block) + return; + read_block(block, blk); + for (i = 0; i < (MINIX_BLOCK_SIZE >> 1); i++) + add_zone(i + (unsigned short *)blk, &chg_blk); + if (chg_blk) + write_block(block, blk); +} + +static void +add_zone_ind2(unsigned int *znr, int *corrected) { + static char blk[MINIX_BLOCK_SIZE]; + int i, chg_blk = 0; + int block; + + block = add_zone2(znr, corrected); + if (!block) + return; + read_block(block, blk); + for (i = 0; i < MINIX_BLOCK_SIZE >> 2; i++) + add_zone2(i + (unsigned int *)blk, &chg_blk); + if (chg_blk) + write_block(block, blk); +} + +static void +add_zone_dind(unsigned short *znr, int *corrected) { + static char blk[MINIX_BLOCK_SIZE]; + int i, blk_chg = 0; + int block; + + block = add_zone(znr, corrected); + if (!block) + return; + read_block(block, blk); + for (i = 0; i < (MINIX_BLOCK_SIZE >> 1); i++) + add_zone_ind(i + (unsigned short *)blk, &blk_chg); + if (blk_chg) + write_block(block, blk); +} + +static void +add_zone_dind2(unsigned int *znr, int *corrected) { + static char blk[MINIX_BLOCK_SIZE]; + int i, blk_chg = 0; + int block; + + block = add_zone2(znr, corrected); + if (!block) + return; + read_block(block, blk); + for (i = 0; i < MINIX_BLOCK_SIZE >> 2; i++) + add_zone_ind2(i + (unsigned int *)blk, &blk_chg); + if (blk_chg) + write_block(block, blk); +} + +static void +add_zone_tind2(unsigned int *znr, int *corrected) { + static char blk[MINIX_BLOCK_SIZE]; + int i, blk_chg = 0; + int block; + + block = add_zone2(znr, corrected); + if (!block) + return; + read_block(block, blk); + for (i = 0; i < MINIX_BLOCK_SIZE >> 2; i++) + add_zone_dind2(i + (unsigned int *)blk, &blk_chg); + if (blk_chg) + write_block(block, blk); +} + +static void +check_zones(unsigned int i) { + struct minix_inode *inode; + + if (!i || i > get_ninodes()) + return; + if (inode_count[i] > 1) /* have we counted this file already? */ + return; + inode = Inode + i; + if (!S_ISDIR(inode->i_mode) && !S_ISREG(inode->i_mode) && + !S_ISLNK(inode->i_mode)) + return; + for (i = 0; i < 7; i++) + add_zone(i + inode->i_zone, &changed); + add_zone_ind(7 + inode->i_zone, &changed); + add_zone_dind(8 + inode->i_zone, &changed); +} + +static void +check_zones2(unsigned int i) { + struct minix2_inode *inode; + + if (!i || i > get_ninodes()) + return; + if (inode_count[i] > 1) /* have we counted this file already? */ + return; + inode = Inode2 + i; + if (!S_ISDIR(inode->i_mode) && !S_ISREG(inode->i_mode) + && !S_ISLNK(inode->i_mode)) + return; + for (i = 0; i < 7; i++) + add_zone2(i + inode->i_zone, &changed); + add_zone_ind2(7 + inode->i_zone, &changed); + add_zone_dind2(8 + inode->i_zone, &changed); + add_zone_tind2(9 + inode->i_zone, &changed); +} + +static void +check_file(struct minix_inode *dir, unsigned int offset) { + static char blk[MINIX_BLOCK_SIZE + 2]; + struct minix_inode *inode; + unsigned int ino; + char *name; + int block; + + block = map_block(dir, offset / MINIX_BLOCK_SIZE); + read_block(block, blk); + name = blk + (offset % MINIX_BLOCK_SIZE) + 2; + ino = *(unsigned short *)(name - 2); + if (ino > get_ninodes()) { + get_current_name(); + printf(_("The directory '%s' contains a bad inode number " + "for file '%.*s'."), current_name, (int)namelen, name); + if (ask(_(" Remove"), 1)) { + *(unsigned short *)(name - 2) = 0; + write_block(block, blk); + } + ino = 0; + } + if (name_depth < MAX_DEPTH) + xstrncpy(name_list[name_depth], name, namelen); + else + return; + name_depth++; + inode = get_inode(ino); + name_depth--; + if (!offset) { + if (!inode || strcmp(".", name)) { + get_current_name(); + printf(_("%s: bad directory: '.' isn't first\n"), + current_name); + errors_uncorrected = 1; + } else + return; + } + if (offset == dirsize) { + if (!inode || strcmp("..", name)) { + get_current_name(); + printf(_("%s: bad directory: '..' isn't second\n"), + current_name); + errors_uncorrected = 1; + } else + return; + } + if (!inode) + return; + if (name_depth < MAX_DEPTH) + xstrncpy(name_list[name_depth], name, namelen); + else + return; + name_depth++; + if (list) { + if (verbose) + printf("%6d %07o %3d ", ino, + inode->i_mode, inode->i_nlinks); + get_current_name(); + printf("%s", current_name); + if (S_ISDIR(inode->i_mode)) + printf(":\n"); + else + printf("\n"); + } + check_zones(ino); + if (inode && S_ISDIR(inode->i_mode)) + recursive_check(ino); + name_depth--; + return; +} + +static void +check_file2(struct minix2_inode *dir, unsigned int offset) { + static char blk[MINIX_BLOCK_SIZE + 4]; + struct minix2_inode *inode; + ino_t ino; + char *name; + int block; + const int version_offset = fs_version == 3 ? 4 : 2; + + block = map_block2(dir, offset / MINIX_BLOCK_SIZE); + read_block(block, blk); + name = blk + (offset % MINIX_BLOCK_SIZE) + version_offset; + ino = version_offset == 4 ? *(uint32_t *)(name - version_offset) + : *(uint16_t *)(name - version_offset); + if (ino > get_ninodes()) { + get_current_name(); + printf(_("The directory '%s' contains a bad inode number " + "for file '%.*s'."), current_name, (int)namelen, name); + if (ask(_(" Remove"), 1)) { + memset(name - version_offset, 0, version_offset); + write_block(block, blk); + } + ino = 0; + } + if (name_depth < MAX_DEPTH) + xstrncpy(name_list[name_depth], name, namelen); + else + return; + name_depth++; + inode = get_inode2(ino); + name_depth--; + if (!offset) { + if (!inode || strcmp(".", name)) { + get_current_name(); + printf(_("%s: bad directory: '.' isn't first\n"), + current_name); + errors_uncorrected = 1; + } else + return; + } + if (offset == dirsize) { + if (!inode || strcmp("..", name)) { + get_current_name(); + printf(_("%s: bad directory: '..' isn't second\n"), + current_name); + errors_uncorrected = 1; + } else + return; + } + if (!inode) + return; + name_depth++; + if (list) { + if (verbose) + printf("%6ju %07o %3d ", (uintmax_t)ino, inode->i_mode, + inode->i_nlinks); + get_current_name(); + printf("%s", current_name); + if (S_ISDIR(inode->i_mode)) + printf(":\n"); + else + printf("\n"); + } + check_zones2(ino); + if (inode && S_ISDIR(inode->i_mode)) + recursive_check2(ino); + name_depth--; + return; +} + +static void +recursive_check(unsigned int ino) { + struct minix_inode *dir; + off_t offset; + + dir = Inode + ino; + if (!S_ISDIR(dir->i_mode)) + die(_("internal error")); + if (dir->i_size < 2 * dirsize) { + get_current_name(); + printf(_("%s: bad directory: size < 32"), current_name); + errors_uncorrected = 1; + } + + if ((!repair || automatic) && !is_valid_zone_nr(*dir->i_zone)) { + get_current_name(); + printf(_("%s: bad directory: invalid i_zone, use --repair to fix\n"), current_name); + return; + } + for (offset = 0; offset < dir->i_size; offset += dirsize) + check_file(dir, offset); +} + +static void +recursive_check2(unsigned int ino) { + struct minix2_inode *dir; + off_t offset; + + dir = Inode2 + ino; + if (!S_ISDIR(dir->i_mode)) + die(_("internal error")); + if (dir->i_size < 2 * dirsize) { + get_current_name(); + printf(_("%s: bad directory: size < 32"), current_name); + errors_uncorrected = 1; + } + for (offset = 0; offset < dir->i_size; offset += dirsize) + check_file2(dir, offset); +} + +static int +bad_zone(int i) { + char buffer[1024]; + + if (MINIX_BLOCK_SIZE * i != lseek(device_fd, MINIX_BLOCK_SIZE * i, SEEK_SET)) + die(_("seek failed in bad_zone")); + return (MINIX_BLOCK_SIZE != read(device_fd, buffer, MINIX_BLOCK_SIZE)); +} + +static void +check_counts(void) { + unsigned long i; + + for (i = 1; i <= get_ninodes(); i++) { + if (!inode_in_use(i) && Inode[i].i_mode && warn_mode) { + printf(_("Inode %lu mode not cleared."), i); + if (ask(_("Clear"), 1)) { + Inode[i].i_mode = 0; + changed = 1; + } + } + if (!inode_count[i]) { + if (!inode_in_use(i)) + continue; + printf(_("Inode %lu not used, marked used in the bitmap."), i); + if (ask(_("Clear"), 1)) + unmark_inode(i); + continue; + } + if (!inode_in_use(i)) { + printf(_("Inode %lu used, marked unused in the bitmap."), i); + if (ask(_("Set"), 1)) + mark_inode(i); + } + if (Inode[i].i_nlinks != inode_count[i]) { + printf(_("Inode %lu (mode = %07o), i_nlinks=%d, counted=%d."), + i, Inode[i].i_mode, Inode[i].i_nlinks, + inode_count[i]); + if (ask(_("Set i_nlinks to count"), 1)) { + Inode[i].i_nlinks = inode_count[i]; + changed = 1; + } + } + } + for (i = get_first_zone(); i < get_nzones(); i++) { + if (zone_in_use(i) == zone_count[i]) + continue; + if (!zone_count[i]) { + if (bad_zone(i)) + continue; + printf(_("Zone %lu: marked in use, no file uses it."), + i); + if (ask(_("Unmark"), 1)) + unmark_zone(i); + continue; + } + if (zone_in_use(i)) + printf(_("Zone %lu: in use, counted=%d\n"), + i, zone_count[i]); + else + printf(_("Zone %lu: not in use, counted=%d\n"), + i, zone_count[i]); + } +} + +static void +check_counts2(void) { + unsigned long i; + + for (i = 1; i <= get_ninodes(); i++) { + if (!inode_in_use(i) && Inode2[i].i_mode && warn_mode) { + printf(_("Inode %lu mode not cleared."), i); + if (ask(_("Clear"), 1)) { + Inode2[i].i_mode = 0; + changed = 1; + } + } + if (!inode_count[i]) { + if (!inode_in_use(i)) + continue; + printf(_("Inode %lu not used, marked used in the bitmap."), i); + if (ask(_("Clear"), 1)) + unmark_inode(i); + continue; + } + if (!inode_in_use(i)) { + printf(_("Inode %lu used, marked unused in the bitmap."), i); + if (ask(_("Set"), 1)) + mark_inode(i); + } + if (Inode2[i].i_nlinks != inode_count[i]) { + printf(_("Inode %lu (mode = %07o), i_nlinks=%d, counted=%d."), + i, Inode2[i].i_mode, Inode2[i].i_nlinks, + inode_count[i]); + if (ask(_("Set i_nlinks to count"), 1)) { + Inode2[i].i_nlinks = inode_count[i]; + changed = 1; + } + } + } + for (i = get_first_zone(); i < get_nzones(); i++) { + if (zone_in_use(i) == zone_count[i]) + continue; + if (!zone_count[i]) { + if (bad_zone(i)) + continue; + printf(_("Zone %lu: marked in use, no file uses it."), + i); + if (ask(_("Unmark"), 1)) + unmark_zone(i); + continue; + } + if (zone_in_use(i)) + printf(_("Zone %lu: in use, counted=%d\n"), + i, zone_count[i]); + else + printf(_("Zone %lu: not in use, counted=%d\n"), + i, zone_count[i]); + } +} + +static void +check(void) { + memset(inode_count, 0, (get_ninodes() + 1) * sizeof(*inode_count)); + memset(zone_count, 0, get_nzones() * sizeof(*zone_count)); + check_zones(ROOT_INO); + recursive_check(ROOT_INO); + check_counts(); +} + +static void +check2(void) { + memset(inode_count, 0, (get_ninodes() + 1) * sizeof(*inode_count)); + memset(zone_count, 0, get_nzones() * sizeof(*zone_count)); + check_zones2(ROOT_INO); + recursive_check2(ROOT_INO); + check_counts2(); +} + +int +main(int argc, char **argv) { + struct termios tmp; + int count; + int retcode = FSCK_EX_OK; + int i; + static const struct option longopts[] = { + {"list", no_argument, NULL, 'l'}, + {"auto", no_argument, NULL, 'a'}, + {"repair", no_argument, NULL, 'r'}, + {"verbose", no_argument, NULL, 'v'}, + {"super", no_argument, NULL, 's'}, + {"uncleared", no_argument, NULL, 'm'}, + {"force", no_argument, NULL, 'f'}, + {"version", no_argument, NULL, 'V'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + strutils_set_exitcode(FSCK_EX_USAGE); + + if (INODE_SIZE * MINIX_INODES_PER_BLOCK != MINIX_BLOCK_SIZE) + die(_("bad inode size")); + if (INODE2_SIZE * MINIX2_INODES_PER_BLOCK != MINIX_BLOCK_SIZE) + die(_("bad v2 inode size")); + + while ((i = getopt_long(argc, argv, "larvsmfVh", longopts, NULL)) != -1) + switch (i) { + case 'l': + list = 1; + break; + case 'a': + automatic = 1; + repair = 1; + break; + case 'r': + automatic = 0; + repair = 1; + break; + case 'v': + verbose = 1; + break; + case 's': + show = 1; + break; + case 'm': + warn_mode = 1; + break; + case 'f': + force = 1; + break; + case 'V': + printf(UTIL_LINUX_VERSION); + return FSCK_EX_OK; + case 'h': + usage(); + default: + errtryhelp(FSCK_EX_USAGE); + } + argc -= optind; + argv += optind; + if (0 < argc) { + device_name = argv[0]; + } else { + warnx(_("no device specified")); + errtryhelp(FSCK_EX_USAGE); + } + check_mount(); /* trying to check a mounted filesystem? */ + if (repair && !automatic && (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO))) + die(_("need terminal for interactive repairs")); + + device_fd = open(device_name, repair ? O_RDWR : O_RDONLY); + if (device_fd < 0) + die(_("cannot open %s: %s"), device_name, strerror(errno)); + for (count = 0; count < 3; count++) + sync(); + read_superblock(); + + /* Determine whether or not we should continue with the checking. This + * is based on the status of the filesystem valid and error flags and + * whether or not the -f switch was specified on the command line. */ + if (fs_version < 3 && !(Super.s_state & MINIX_ERROR_FS) && + (Super.s_state & MINIX_VALID_FS) && !force) { + if (repair) + printf(_("%s is clean, no check.\n"), device_name); + return retcode; + } else if (force) + printf(_("Forcing filesystem check on %s.\n"), device_name); + else if (repair) + printf(_("Filesystem on %s is dirty, needs checking.\n"), + device_name); + + read_tables(); + + /* Restore the terminal state on fatal signals. We don't do this for + * SIGALRM, SIGUSR1 or SIGUSR2. */ + signal(SIGINT, fatalsig); + signal(SIGQUIT, fatalsig); + signal(SIGTERM, fatalsig); + + if (repair && !automatic) { + tcgetattr(STDIN_FILENO, &termios); + tmp = termios; + tmp.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &tmp); + termios_set = 1; + } + + if (fs_version == 2 || fs_version == 3) { + check_root2(); + check2(); + } else { + check_root(); + check(); + } + if (verbose) { + unsigned long inode, free; + + for (inode = 1, free = 0; inode <= get_ninodes(); inode++) + if (!inode_in_use(inode)) + free++; + printf(_("\n%6ld inodes used (%ld%%)\n"), + (get_ninodes() - free), + 100 * (get_ninodes() - free) / get_ninodes()); + for (inode = get_first_zone(), free = 0; inode < get_nzones(); inode++) + if (!zone_in_use(inode)) + free++; + printf(_("%6ld zones used (%ld%%)\n"), (get_nzones() - free), + 100 * (get_nzones() - free) / get_nzones()); + printf(_("\n%6d regular files\n" + "%6d directories\n" + "%6d character device files\n" + "%6d block device files\n" + "%6d links\n" + "%6d symbolic links\n" + "------\n" + "%6d files\n"), + regular, directory, chardev, blockdev, + links - 2 * directory + 1, symlinks, + total - 2 * directory + 1); + } + if (changed) { + write_tables(); + printf(_("----------------------------\n" + "FILE SYSTEM HAS BEEN CHANGED\n" + "----------------------------\n")); + for (count = 0; count < 3; count++) + sync(); + } else if (repair) + write_super_block(); + + if (repair && !automatic) + tcsetattr(STDIN_FILENO, TCSANOW, &termios); + + if (close_fd(device_fd) != 0) + err(FSCK_EX_ERROR, _("write failed")); + if (changed) + retcode += 3; + if (errors_uncorrected) + retcode += 4; + return retcode; +} |