| /* |
| * 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 rudimetary 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). |
| * |
| * Usuage: fsck [-larvsm] device |
| * -l for a listing of all the filenames |
| * -a for automatic repairs (not implemented) |
| * -r for repairs (interactive) (not implemented) |
| * -v for verbose (tells how many files) |
| * -s for super-block info |
| * -m for minix-like "mode not cleared" warnings |
| * -f force filesystem check even if filesystem marked as valid |
| * |
| * The device may be a block device or a image of one, but this isn't |
| * enforced (but it's not much fun on a character device :-). |
| */ |
| |
| #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 <mntent.h> |
| #include <sys/stat.h> |
| #include <signal.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 fuctions */ |
| 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 IN; |
| 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]; |
| |
| #define MAGIC (Super.s_magic) |
| |
| 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 |
| leave(int status) { |
| reset(); |
| exit(status); |
| } |
| |
| static void |
| usage(void) { |
| fputs(USAGE_HEADER, stderr); |
| fprintf(stderr, |
| _(" %s [options] <device>\n"), program_invocation_short_name); |
| |
| fputs(USAGE_SEPARATOR, stderr); |
| fputs(_("Check the consistency of a Minix filesystem.\n"), stderr); |
| |
| fputs(USAGE_OPTIONS, stderr); |
| fputs(_(" -l list all filenames\n"), stderr); |
| fputs(_(" -a automatic repair\n"), stderr); |
| fputs(_(" -r interactive repair\n"), stderr); |
| fputs(_(" -v be verbose\n"), stderr); |
| fputs(_(" -s output super-block information\n"), stderr); |
| fputs(_(" -m activate mode not cleared warnings\n"), stderr); |
| fputs(_(" -f force check\n"), stderr); |
| fputs(USAGE_SEPARATOR, stderr); |
| fputs(USAGE_VERSION, stderr); |
| fprintf(stderr, USAGE_MAN_TAIL("fsck.minix(8)")); |
| leave(FSCK_EX_USAGE); |
| } |
| |
| 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; |
| } |
| |
| /* 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(IN, 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(IN, 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(IN, MINIX_BLOCK_SIZE * nr, SEEK_SET)) |
| die(_("seek failed in write_block")); |
| if (MINIX_BLOCK_SIZE != write(IN, 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; |
| |
| 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; |
| result = check_zone_nr(dind + (blknr / 512), &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) { |
| /* 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(IN, MINIX_BLOCK_SIZE, SEEK_SET)) |
| die(_("seek failed in write_super_block")); |
| if (MINIX_BLOCK_SIZE != write(IN, 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(IN, inode_map, imaps * MINIX_BLOCK_SIZE)) |
| die(_("Unable to write inode map")); |
| |
| if (write_all(IN, zone_map, zmaps * MINIX_BLOCK_SIZE)) |
| die(_("Unable to write zone map")); |
| |
| if (write_all(IN, 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) |
| 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(IN, 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(IN, super_block_buffer, MINIX_BLOCK_SIZE)) |
| die(_("unable to read super block")); |
| if (MAGIC == MINIX_SUPER_MAGIC) { |
| namelen = 14; |
| dirsize = 16; |
| fs_version = 1; |
| } else if (MAGIC == MINIX_SUPER_MAGIC2) { |
| namelen = 30; |
| dirsize = 32; |
| fs_version = 1; |
| } else if (MAGIC == MINIX2_SUPER_MAGIC) { |
| namelen = 14; |
| dirsize = 16; |
| fs_version = 2; |
| } else if (MAGIC == MINIX2_SUPER_MAGIC2) { |
| namelen = 30; |
| dirsize = 32; |
| fs_version = 2; |
| } 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_nimaps() * MINIX_BLOCK_SIZE * 8 < get_ninodes() + 1) |
| die(_("bad s_imap_blocks 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(IN, inode_map, imaps * MINIX_BLOCK_SIZE); |
| if (rc < 0 || imaps * MINIX_BLOCK_SIZE != (size_t) rc) |
| die(_("Unable to read inode map")); |
| |
| rc = read(IN, zone_map, zmaps * MINIX_BLOCK_SIZE); |
| if (rc < 0 || zmaps * MINIX_BLOCK_SIZE != (size_t) rc) |
| die(_("Unable to read zone map")); |
| |
| rc = read(IN, 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"), first_zone, norm_first_zone); |
| printf(_("Zonesize=%d\n"), MINIX_BLOCK_SIZE << get_zone_size()); |
| printf(_("Maxsize=%zu\n"), get_max_size()); |
| 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]; |
| 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]; |
| struct minix2_inode *inode; |
| ino_t ino; |
| char *name; |
| int block; |
| |
| block = map_block2(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_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 ", 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; |
| } |
| 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(IN, MINIX_BLOCK_SIZE * i, SEEK_SET)) |
| die(_("seek failed in bad_zone")); |
| return (MINIX_BLOCK_SIZE != read(IN, 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; |
| |
| setlocale(LC_ALL, ""); |
| bindtextdomain(PACKAGE, LOCALEDIR); |
| textdomain(PACKAGE); |
| atexit(close_stdout); |
| |
| if (argc == 2 && |
| (!strcmp(argv[1], "-V") || !strcmp(argv[1], "--version"))) { |
| printf(UTIL_LINUX_VERSION); |
| exit(FSCK_EX_OK); |
| } |
| |
| 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 (argc-- > 1) { |
| argv++; |
| if (argv[0][0] != '-') { |
| if (device_name) |
| usage(); |
| else |
| device_name = argv[0]; |
| } else |
| while (*++argv[0]) |
| switch (argv[0][0]) { |
| 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; |
| default: |
| usage(); |
| } |
| } |
| if (!device_name) |
| usage(); |
| check_mount(); /* trying to check a mounted filesystem? */ |
| if (repair && !automatic) { |
| if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) |
| die(_("need terminal for interactive repairs")); |
| } |
| IN = open(device_name, repair ? O_RDWR : O_RDONLY); |
| if (IN < 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 (!(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) { |
| check_root2(); |
| check2(); |
| } else { |
| check_root(); |
| check(); |
| } |
| if (verbose) { |
| unsigned long i, free; |
| |
| for (i = 1, free = 0; i <= get_ninodes(); i++) |
| if (!inode_in_use(i)) |
| free++; |
| printf(_("\n%6ld inodes used (%ld%%)\n"), |
| (get_ninodes() - free), |
| 100 * (get_ninodes() - free) / get_ninodes()); |
| for (i = get_first_zone(), free = 0; i < get_nzones(); i++) |
| if (!zone_in_use(i)) |
| 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(IN) != 0) |
| err(FSCK_EX_ERROR, _("write failed")); |
| if (changed) |
| retcode += 3; |
| if (errors_uncorrected) |
| retcode += 4; |
| return retcode; |
| } |