]> git.ipfire.org Git - thirdparty/util-linux.git/blobdiff - disk-utils/fsck.minix.c
include/pidfd-utils: remove hardcoded syscall fallback
[thirdparty/util-linux.git] / disk-utils / fsck.minix.c
index 5e56e832dca748c750fe9de73a3dd090926ed4dd..08903587b6f2bc7277d9d3b61c98b56ceabc322c 100644 (file)
@@ -1,12 +1,18 @@
 /*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
  * 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
+ *
+ *
+ * 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.
@@ -22,7 +28,7 @@
  *
  *
  * 19.04.92  - Had to start over again from this old version, as a
- *             kernel bug ate my enhanced fsck in february.
+ *             kernel bug ate my enhanced fsck in February.
  *
  * 28.02.93  - added support for different directory entry sizes..
  *
@@ -62,7 +68,7 @@
  * 06.11.96  - Added v2 code submitted by Joerg Dorchain, but written by
  *             Andreas Schwab.
  *
- * 1999-02-22 Arkadiusz Mikiewicz <misiek@pld.ORG.PL>
+ * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
  * - added Native Language Support
  *
  * 2008-04-06 James Youngman <jay@gnu.org>
  * 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>
@@ -98,9 +93,9 @@
 #include <ctype.h>
 #include <stdlib.h>
 #include <termios.h>
-#include <mntent.h>
 #include <sys/stat.h>
 #include <signal.h>
+#include <getopt.h>
 
 #include "c.h"
 #include "exitcodes.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 */
+/* Global variables used in minix_programs.h inline functions */
 int fs_version = 1;
 char *super_block_buffer;
 
@@ -125,7 +122,7 @@ static char *inode_buffer;
 #define Inode2 (((struct minix2_inode *) inode_buffer) - 1)
 
 static char *device_name;
-static int IN;
+static int device_fd;
 static int repair, automatic, verbose, list, show, warn_mode, force;
 static int directory, regular, blockdev, chardev, links, symlinks, total;
 
@@ -145,8 +142,6 @@ static char name_list[MAX_DEPTH][MINIX_NAME_MAX + 1];
  * 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;
 
@@ -168,7 +163,7 @@ static char *zone_map;
 static void
 reset(void) {
        if (termios_set)
-               tcsetattr(0, TCSANOW, &termios);
+               tcsetattr(STDIN_FILENO, TCSANOW, &termios);
 }
 
 static void
@@ -181,29 +176,31 @@ fatalsig(int sig) {
        raise(sig);
 }
 
-static void
+static void __attribute__((__noreturn__))
 leave(int status) {
        reset();
        exit(status);
 }
 
-static void
+static void __attribute__((__noreturn__))
 usage(void) {
-       fputs(USAGE_HEADER, stderr);
-       fprintf(stderr,
-               _(" %s [options] <device>\n"), program_invocation_short_name);
-       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);
+       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);
+       fprintf(out, USAGE_HELP_OPTIONS(18));
+       fprintf(out, USAGE_MAN_TAIL("fsck.minix(8)"));
+       exit(FSCK_EX_OK);
 }
 
 static void die(const char *fmt, ...)
@@ -213,7 +210,7 @@ static void
 die(const char *fmt, ...) {
        va_list ap;
 
-       fprintf(stderr, "%s: ", program_invocation_short_name);
+       fprintf(stderr, UTIL_LINUX_VERSION);
        va_start(ap, fmt);
        vfprintf(stderr, fmt, ap);
        va_end(ap);
@@ -263,11 +260,11 @@ ask(const char *string, int def) {
        ignore_result( fgets(input, YESNO_LENGTH, stdin) );
        resp = rpmatch(input);
        switch (resp) {
-       case -1:
+       case RPMATCH_INVALID:
                /* def = def */
                break;
-       case 0:
-       case 1:
+       case RPMATCH_NO:
+       case RPMATCH_YES:
                def = resp;
                break;
        default:
@@ -294,7 +291,7 @@ check_mount(void) {
                return;
 
        printf(_("%s is mounted.         "), device_name);
-       if (isatty(0) && isatty(1))
+       if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO))
                cont = ask(_("Do you really want to continue"), 0);
        else
                cont = 0;
@@ -302,7 +299,16 @@ check_mount(void) {
                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;
+       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
@@ -357,13 +363,13 @@ read_block(unsigned int nr, char *addr) {
                memset(addr, 0, MINIX_BLOCK_SIZE);
                return;
        }
-       if (MINIX_BLOCK_SIZE * nr != lseek(IN, MINIX_BLOCK_SIZE * nr, SEEK_SET)) {
+       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(IN, addr, MINIX_BLOCK_SIZE)) {
+       } 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);
@@ -382,9 +388,9 @@ write_block(unsigned int nr, char *addr) {
                errors_uncorrected = 1;
                return;
        }
-       if (MINIX_BLOCK_SIZE * nr != lseek(IN, MINIX_BLOCK_SIZE * nr, SEEK_SET))
+       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(IN, addr, MINIX_BLOCK_SIZE)) {
+       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);
@@ -400,6 +406,7 @@ 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);
@@ -417,7 +424,12 @@ map_block(struct minix_inode *inode, unsigned int blknr) {
        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);
+       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;
@@ -488,6 +500,9 @@ map_block2(struct minix2_inode *inode, unsigned int blknr) {
 
 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.  */
@@ -497,27 +512,27 @@ write_super_block(void) {
        else
                Super.s_state &= ~MINIX_ERROR_FS;
 
-       if (MINIX_BLOCK_SIZE != lseek(IN, MINIX_BLOCK_SIZE, SEEK_SET))
+       if (MINIX_BLOCK_SIZE != lseek(device_fd, MINIX_BLOCK_SIZE, SEEK_SET))
                die(_("seek failed in write_super_block"));
-       if (MINIX_BLOCK_SIZE != write(IN, super_block_buffer, MINIX_BLOCK_SIZE))
+       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) {
-       write_super_block();
        unsigned long buffsz = get_inode_buffer_size();
        unsigned long imaps = get_nimaps();
        unsigned long zmaps = get_nzmaps();
 
-       if (write_all(IN, inode_map, imaps * MINIX_BLOCK_SIZE))
+       write_super_block();
+
+       if (write_all(device_fd, inode_map, imaps * MINIX_BLOCK_SIZE))
                die(_("Unable to write inode map"));
 
-       if (write_all(IN, zone_map, zmaps * MINIX_BLOCK_SIZE))
+       if (write_all(device_fd, zone_map, zmaps * MINIX_BLOCK_SIZE))
                die(_("Unable to write zone map"));
 
-       if (write_all(IN, inode_buffer, buffsz))
+       if (write_all(device_fd, inode_buffer, buffsz))
                die(_("Unable to write inodes"));
 }
 
@@ -527,13 +542,13 @@ get_dirsize(void) {
        char blk[MINIX_BLOCK_SIZE];
        size_t size;
 
-       if (fs_version == 2)
+       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) {
+       for (size = 16; size + 2 < MINIX_BLOCK_SIZE; size <<= 1) {
                if (strcmp(blk + size + 2, "..") == 0) {
                        dirsize = size;
                        namelen = size - 2;
@@ -545,37 +560,45 @@ get_dirsize(void) {
 
 static void
 read_superblock(void) {
-       if (MINIX_BLOCK_SIZE != lseek(IN, MINIX_BLOCK_SIZE, SEEK_SET))
+       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(IN, super_block_buffer, MINIX_BLOCK_SIZE))
+       if (MINIX_BLOCK_SIZE != read(device_fd, super_block_buffer, MINIX_BLOCK_SIZE))
                die(_("unable to read super block"));
-       if (MAGIC == MINIX_SUPER_MAGIC) {
+       if (Super.s_magic == MINIX_SUPER_MAGIC) {
                namelen = 14;
                dirsize = 16;
                fs_version = 1;
-       } else if (MAGIC == MINIX_SUPER_MAGIC2) {
+       } else if (Super.s_magic == MINIX_SUPER_MAGIC2) {
                namelen = 30;
                dirsize = 32;
                fs_version = 1;
-       } else if (MAGIC == MINIX2_SUPER_MAGIC) {
+       } else if (Super.s_magic == MINIX2_SUPER_MAGIC) {
                namelen = 14;
                dirsize = 16;
                fs_version = 2;
-       } else if (MAGIC == MINIX2_SUPER_MAGIC2) {
+       } 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"));
@@ -584,9 +607,9 @@ read_superblock(void) {
 static void
 read_tables(void) {
        unsigned long inodes = get_ninodes();
-       unsigned long buffsz = get_inode_buffer_size();
-       unsigned long norm_first_zone = first_zone_data();
-       unsigned long first_zone = get_first_zone();
+       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();
@@ -608,15 +631,15 @@ read_tables(void) {
        if (!zone_count)
                die(_("Unable to allocate buffer for zone count"));
 
-       rc = read(IN, inode_map, imaps * MINIX_BLOCK_SIZE);
+       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(IN, zone_map, zmaps * MINIX_BLOCK_SIZE);
+       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(IN, inode_buffer, buffsz);
+       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) {
@@ -627,10 +650,12 @@ read_tables(void) {
        if (show) {
                printf(_("%ld inodes\n"), inodes);
                printf(_("%ld blocks\n"), zones);
-               printf(_("Firstdatazone=%ld (%ld)\n"), first_zone, norm_first_zone);
+               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=%ld\n"), get_max_size());
-               printf(_("Filesystem state=%d\n"), Super.s_state);
+               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);
        }
 }
@@ -929,7 +954,7 @@ check_zones2(unsigned int i) {
 
 static void
 check_file(struct minix_inode *dir, unsigned int offset) {
-       static char blk[MINIX_BLOCK_SIZE];
+       static char blk[MINIX_BLOCK_SIZE + 2];
        struct minix_inode *inode;
        unsigned int ino;
        char *name;
@@ -950,12 +975,14 @@ check_file(struct minix_inode *dir, unsigned int offset) {
                ino = 0;
        }
        if (name_depth < MAX_DEPTH)
-               strncpy(name_list[name_depth], name, namelen);
+               xstrncpy(name_list[name_depth], name, namelen);
+       else
+               return;
        name_depth++;
        inode = get_inode(ino);
        name_depth--;
        if (!offset) {
-               if (!inode || strcmp(".", name)) {
+               if (!inode || strcmp(".", name) != 0) {
                        get_current_name();
                        printf(_("%s: bad directory: '.' isn't first\n"),
                               current_name);
@@ -964,7 +991,7 @@ check_file(struct minix_inode *dir, unsigned int offset) {
                        return;
        }
        if (offset == dirsize) {
-               if (!inode || strcmp("..", name)) {
+               if (!inode || strcmp("..", name) != 0) {
                        get_current_name();
                        printf(_("%s: bad directory: '..' isn't second\n"),
                               current_name);
@@ -975,7 +1002,9 @@ check_file(struct minix_inode *dir, unsigned int offset) {
        if (!inode)
                return;
        if (name_depth < MAX_DEPTH)
-               strncpy(name_list[name_depth], name, namelen);
+               xstrncpy(name_list[name_depth], name, namelen);
+       else
+               return;
        name_depth++;
        if (list) {
                if (verbose)
@@ -992,38 +1021,41 @@ check_file(struct minix_inode *dir, unsigned int offset) {
        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];
+       static char blk[MINIX_BLOCK_SIZE + 4];
        struct minix2_inode *inode;
-       unsigned long ino;
+       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) + 2;
-       ino = *(unsigned short *)(name - 2);
+       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)) {
-                       *(unsigned short *)(name - 2) = 0;
+                       memset(name - version_offset, 0, version_offset);
                        write_block(block, blk);
                }
                ino = 0;
        }
        if (name_depth < MAX_DEPTH)
-               strncpy(name_list[name_depth], name, namelen);
+               xstrncpy(name_list[name_depth], name, namelen);
+       else
+               return;
        name_depth++;
        inode = get_inode2(ino);
        name_depth--;
        if (!offset) {
-               if (!inode || strcmp(".", name)) {
+               if (!inode || strcmp(".", name) != 0) {
                        get_current_name();
                        printf(_("%s: bad directory: '.' isn't first\n"),
                               current_name);
@@ -1032,7 +1064,7 @@ check_file2(struct minix2_inode *dir, unsigned int offset) {
                        return;
        }
        if (offset == dirsize) {
-               if (!inode || strcmp("..", name)) {
+               if (!inode || strcmp("..", name) != 0) {
                        get_current_name();
                        printf(_("%s: bad directory: '..' isn't second\n"),
                               current_name);
@@ -1045,7 +1077,7 @@ check_file2(struct minix2_inode *dir, unsigned int offset) {
        name_depth++;
        if (list) {
                if (verbose)
-                       printf("%6zd %07o %3d ", ino, inode->i_mode,
+                       printf("%6ju %07o %3d ", (uintmax_t)ino, inode->i_mode,
                               inode->i_nlinks);
                get_current_name();
                printf("%s", current_name);
@@ -1058,13 +1090,12 @@ check_file2(struct minix2_inode *dir, unsigned int offset) {
        if (inode && S_ISDIR(inode->i_mode))
                recursive_check2(ino);
        name_depth--;
-       return;
 }
 
 static void
 recursive_check(unsigned int ino) {
        struct minix_inode *dir;
-       unsigned int offset;
+       off_t offset;
 
        dir = Inode + ino;
        if (!S_ISDIR(dir->i_mode))
@@ -1074,6 +1105,12 @@ recursive_check(unsigned int ino) {
                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);
 }
@@ -1081,7 +1118,7 @@ recursive_check(unsigned int ino) {
 static void
 recursive_check2(unsigned int ino) {
        struct minix2_inode *dir;
-       unsigned int offset;
+       off_t offset;
 
        dir = Inode2 + ino;
        if (!S_ISDIR(dir->i_mode))
@@ -1099,9 +1136,9 @@ static int
 bad_zone(int i) {
        char buffer[1024];
 
-       if (MINIX_BLOCK_SIZE * i != lseek(IN, MINIX_BLOCK_SIZE * i, SEEK_SET))
+       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(IN, buffer, MINIX_BLOCK_SIZE));
+       return (MINIX_BLOCK_SIZE != read(device_fd, buffer, MINIX_BLOCK_SIZE));
 }
 
 static void
@@ -1238,71 +1275,80 @@ int
 main(int argc, char **argv) {
        struct termios tmp;
        int count;
-       int retcode = 0;
+       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);
+       close_stdout_atexit();
 
-       if (argc == 2 &&
-           (!strcmp(argv[1], "-V") || !strcmp(argv[1], "--version"))) {
-               printf(UTIL_LINUX_VERSION);
-               exit(FSCK_EX_OK);
-       }
+       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 (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();
-                               }
+       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':
+                       print_version(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);
        }
-       if (!device_name)
-               usage();
        check_mount();          /* trying to check a mounted filesystem? */
-       if (repair && !automatic) {
-               if (!isatty(0) || !isatty(1))
-                       die(_("need terminal for interactive repairs"));
-       }
-       IN = open(device_name, repair ? O_RDWR : O_RDONLY);
-       if (IN < 0)
-               die(_("unable to open '%s': %s"), device_name, strerror(errno));
+       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();
@@ -1310,12 +1356,14 @@ main(int argc, char **argv) {
        /* 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) &&
+       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)
+       }
+
+       if (force)
                printf(_("Forcing filesystem check on %s.\n"), device_name);
        else if (repair)
                printf(_("Filesystem on %s is dirty, needs checking.\n"),
@@ -1330,14 +1378,14 @@ main(int argc, char **argv) {
        signal(SIGTERM, fatalsig);
 
        if (repair && !automatic) {
-               tcgetattr(0, &termios);
+               tcgetattr(STDIN_FILENO, &termios);
                tmp = termios;
                tmp.c_lflag &= ~(ICANON | ECHO);
-               tcsetattr(0, TCSANOW, &tmp);
+               tcsetattr(STDIN_FILENO, TCSANOW, &tmp);
                termios_set = 1;
        }
 
-       if (fs_version == 2) {
+       if (fs_version == 2 || fs_version == 3) {
                check_root2();
                check2();
        } else {
@@ -1345,16 +1393,16 @@ main(int argc, char **argv) {
                check();
        }
        if (verbose) {
-               unsigned long i, free;
+               unsigned long inode, free;
 
-               for (i = 1, free = 0; i <= get_ninodes(); i++)
-                       if (!inode_in_use(i))
+               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 (i = get_first_zone(), free = 0; i < get_nzones(); i++)
-                       if (!zone_in_use(i))
+               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());
@@ -1381,8 +1429,10 @@ main(int argc, char **argv) {
                write_super_block();
 
        if (repair && !automatic)
-               tcsetattr(0, TCSANOW, &termios);
+               tcsetattr(STDIN_FILENO, TCSANOW, &termios);
 
+       if (close_fd(device_fd) != 0)
+               err(FSCK_EX_ERROR, _("write failed"));
        if (changed)
                retcode += 3;
        if (errors_uncorrected)