/* * unix.c - The unix-specific code for e2fsck * * Copyright (C) 1993, 1994, 1995, 1996, 1997 Theodore Ts'o. * * %Begin-Header% * This file may be redistributed under the terms of the GNU Public * License. * %End-Header% */ #include #ifdef HAVE_STDLIB_H #include #endif #include #include #include #include #include #ifdef HAVE_GETOPT_H #include #endif #include #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_MNTENT_H #include #endif #include #include #include "et/com_err.h" #include "e2fsck.h" #include "problem.h" #include "../version.h" extern int isatty(int); /* Command line options */ static int blocksize = 0; static int swapfs = 0; static int normalize_swapfs = 0; static int cflag = 0; /* check disk */ static int show_version_only = 0; static int force = 0; static int verbose = 0; static int replace_bad_blocks = 0; static char *bad_blocks_file = 0; static int possible_block_sizes[] = { 1024, 2048, 4096, 8192, 0}; static int root_filesystem = 0; static int read_only_root = 0; static void usage(e2fsck_t ctx) { fprintf(stderr, "Usage: %s [-panyrcdfvstFSV] [-b superblock] [-B blocksize]\n" "\t\t[-I inode_buffer_blocks] [-P process_inode_size]\n" "\t\t[-l|-L bad_blocks_file] device\n", ctx->program_name); exit(FSCK_USAGE); } static void show_stats(e2fsck_t ctx) { ext2_filsys fs = ctx->fs; int inodes, inodes_used, blocks, blocks_used; int dir_links; int num_files, num_links; int frag_percent; dir_links = 2 * ctx->fs_directory_count - 1; num_files = ctx->fs_total_count - dir_links; num_links = ctx->fs_links_count - dir_links; inodes = fs->super->s_inodes_count; inodes_used = (fs->super->s_inodes_count - fs->super->s_free_inodes_count); blocks = fs->super->s_blocks_count; blocks_used = (fs->super->s_blocks_count - fs->super->s_free_blocks_count); frag_percent = (10000 * ctx->fs_fragmented) / inodes_used; frag_percent = (frag_percent + 5) / 10; if (!verbose) { printf("%s: %d/%d files (%0d.%d%% non-contiguous), %d/%d blocks\n", ctx->device_name, inodes_used, inodes, frag_percent / 10, frag_percent % 10, blocks_used, blocks); return; } printf ("\n%8d inode%s used (%d%%)\n", inodes_used, (inodes_used != 1) ? "s" : "", 100 * inodes_used / inodes); printf ("%8d non-contiguous inodes (%0d.%d%%)\n", ctx->fs_fragmented, frag_percent / 10, frag_percent % 10); printf (" # of inodes with ind/dind/tind blocks: %d/%d/%d\n", ctx->fs_ind_count, ctx->fs_dind_count, ctx->fs_tind_count); printf ("%8d block%s used (%d%%)\n" "%8d bad block%s\n", blocks_used, (blocks_used != 1) ? "s" : "", 100 * blocks_used / blocks, ctx->fs_badblocks_count, ctx->fs_badblocks_count != 1 ? "s" : ""); printf ("\n%8d regular file%s\n" "%8d director%s\n" "%8d character device file%s\n" "%8d block device file%s\n" "%8d fifo%s\n" "%8d link%s\n" "%8d symbolic link%s (%d fast symbolic link%s)\n" "%8d socket%s\n" "--------\n" "%8d file%s\n", ctx->fs_regular_count, (ctx->fs_regular_count != 1) ? "s" : "", ctx->fs_directory_count, (ctx->fs_directory_count != 1) ? "ies" : "y", ctx->fs_chardev_count, (ctx->fs_chardev_count != 1) ? "s" : "", ctx->fs_blockdev_count, (ctx->fs_blockdev_count != 1) ? "s" : "", ctx->fs_fifo_count, (ctx->fs_fifo_count != 1) ? "s" : "", ctx->fs_links_count - dir_links, ((ctx->fs_links_count - dir_links) != 1) ? "s" : "", ctx->fs_symlinks_count, (ctx->fs_symlinks_count != 1) ? "s" : "", ctx->fs_fast_symlinks_count, (ctx->fs_fast_symlinks_count != 1) ? "s" : "", ctx->fs_sockets_count, (ctx->fs_sockets_count != 1) ? "s" : "", ctx->fs_total_count - dir_links, ((ctx->fs_total_count - dir_links) != 1) ? "s" : ""); } static void check_mount(e2fsck_t ctx) { errcode_t retval; int mount_flags, cont, fd; retval = ext2fs_check_if_mounted(ctx->filesystem_name, &mount_flags); if (retval) { com_err("ext2fs_check_if_mount", retval, "while determining whether %s is mounted.", ctx->filesystem_name); return; } if (!(mount_flags & EXT2_MF_MOUNTED)) return; #if (defined(__linux__) && defined(HAVE_MNTENT_H)) /* * If the root is mounted read-only, then /etc/mtab is * probably not correct; so we won't issue a warning based on * it. */ fd = open(MOUNTED, O_RDWR); if (fd < 0) { if (errno == EROFS) return; } else close(fd); #endif if (ctx->options & E2F_OPT_READONLY) { printf("Warning! %s is mounted.\n", ctx->device_name); return; } printf("%s is mounted.\n\n", ctx->device_name); printf("\007\007\007\007WARNING!!! Running e2fsck on a mounted filesystem " "may cause\nSEVERE filesystem damage.\007\007\007\n\n"); if (isatty (0) && isatty (1)) cont = ask_yn("Do you really want to continue", -1); else cont = 0; if (!cont) { printf ("check aborted.\n"); exit (0); } return; } /* * This routine checks to see if a filesystem can be skipped; if so, * it will exit with E2FSCK_OK. Under some conditions it will print a * message explaining why a check is being forced. */ static void check_if_skip(e2fsck_t ctx) { ext2_filsys fs = ctx->fs; const char *reason = NULL; if (force || bad_blocks_file || cflag || swapfs) return; if (fs->super->s_state & EXT2_ERROR_FS) reason = "contains a file system with errors"; else if ((fs->super->s_state & EXT2_VALID_FS) == 0) reason = "was not cleanly unmounted"; else if (fs->super->s_mnt_count >= (unsigned) fs->super->s_max_mnt_count) reason = "has reached maximal mount count"; else if (fs->super->s_checkinterval && time(0) >= (fs->super->s_lastcheck + fs->super->s_checkinterval)) reason = "has gone too long without being checked"; if (reason) { printf("%s %s, check forced.\n", ctx->device_name, reason); return; } printf("%s: clean, %d/%d files, %d/%d blocks\n", ctx->device_name, fs->super->s_inodes_count - fs->super->s_free_inodes_count, fs->super->s_inodes_count, fs->super->s_blocks_count - fs->super->s_free_blocks_count, fs->super->s_blocks_count); ext2fs_close(fs); exit(FSCK_OK); } /* * For completion notice */ static int e2fsck_update_progress(e2fsck_t ctx, int pass, unsigned long cur, unsigned long max) { const char spinner[] = "\\|/-"; char buf[80]; if (pass == 0) return 0; if (ctx->progress_fd) { sprintf(buf, "%d %lu %lu\n", pass, cur, max); write(ctx->progress_fd, buf, strlen(buf)); } else { ctx->progress_pos = (ctx->progress_pos+1) & 3; fputc(spinner[ctx->progress_pos], stdout); fputc('\b', stdout); fflush(stdout); } return 0; } #define PATH_SET "PATH=/sbin" static void reserve_stdio_fds(NOARGS) { int fd; while (1) { fd = open("/dev/null", O_RDWR); if (fd > 2) break; } close(fd); } static errcode_t PRS(int argc, char *argv[], e2fsck_t *ret_ctx) { int flush = 0; int c; #ifdef MTRACE extern void *mallwatch; #endif char *oldpath = getenv("PATH"); e2fsck_t ctx; errcode_t retval; retval = e2fsck_allocate_context(&ctx); if (retval) return retval; *ret_ctx = ctx; /* Update our PATH to include /sbin */ if (oldpath) { char *newpath; newpath = (char *) malloc(sizeof (PATH_SET) + 1 + strlen (oldpath)); if (!newpath) fatal_error(ctx, "Couldn't malloc() newpath"); strcpy (newpath, PATH_SET); strcat (newpath, ":"); strcat (newpath, oldpath); putenv (newpath); } else putenv (PATH_SET); setbuf(stdout, NULL); setbuf(stderr, NULL); initialize_ext2_error_table(); if (argc && *argv) ctx->program_name = *argv; else ctx->program_name = "e2fsck"; while ((c = getopt (argc, argv, "panyrcC:B:dfvtFVM:b:I:P:l:L:N:Ss")) != EOF) switch (c) { case 'C': ctx->progress = e2fsck_update_progress; ctx->progress_fd = atoi(optarg); break; case 'p': case 'a': ctx->options |= E2F_OPT_PREEN; ctx->options &= ~(E2F_OPT_YES|E2F_OPT_NO); break; case 'n': ctx->options |= E2F_OPT_NO; ctx->options &= ~(E2F_OPT_YES|E2F_OPT_PREEN); break; case 'y': ctx->options |= E2F_OPT_YES; ctx->options &= ~(E2F_OPT_PREEN|E2F_OPT_NO); break; case 't': #ifdef RESOURCE_TRACK if (ctx->options & E2F_OPT_TIME) ctx->options |= E2F_OPT_TIME2; else ctx->options |= E2F_OPT_TIME; #else fprintf(stderr, "The -t option is not " "supported on this version of e2fsck.\n"); #endif break; case 'c': cflag++; ctx->options |= E2F_OPT_CHECKBLOCKS; break; case 'r': /* What we do by default, anyway! */ break; case 'b': ctx->use_superblock = atoi(optarg); break; case 'B': blocksize = atoi(optarg); break; case 'I': ctx->inode_buffer_blocks = atoi(optarg); break; case 'P': ctx->process_inode_size = atoi(optarg); break; case 'L': replace_bad_blocks++; case 'l': bad_blocks_file = (char *) malloc(strlen(optarg)+1); if (!bad_blocks_file) fatal_error(ctx, "Couldn't malloc bad_blocks_file"); strcpy(bad_blocks_file, optarg); break; case 'd': ctx->options |= E2F_OPT_DEBUG; break; case 'f': force = 1; break; case 'F': #ifdef BLKFLSBUF flush = 1; #else fatal_error(ctx, "-F not supported"); #endif break; case 'v': verbose = 1; break; case 'V': show_version_only = 1; break; #ifdef MTRACE case 'M': mallwatch = (void *) strtol(optarg, NULL, 0); break; #endif case 'N': ctx->device_name = optarg; break; case 's': normalize_swapfs = 1; case 'S': swapfs = 1; break; default: usage(ctx); } if (show_version_only) return 0; if (optind != argc - 1) usage(ctx); if ((ctx->options & E2F_OPT_NO) && !bad_blocks_file && !cflag && !swapfs) ctx->options |= E2F_OPT_READONLY; ctx->filesystem_name = argv[optind]; if (ctx->device_name == 0) ctx->device_name = ctx->filesystem_name; if (flush) { #ifdef BLKFLSBUF int fd = open(ctx->filesystem_name, O_RDONLY, 0); if (fd < 0) { com_err("open", errno, "while opening %s for flushing", ctx->filesystem_name); exit(FSCK_ERROR); } if (ioctl(fd, BLKFLSBUF, 0) < 0) { com_err("BLKFLSBUF", errno, "while trying to flush %s", ctx->filesystem_name); exit(FSCK_ERROR); } close(fd); #else fatal_error(ctx, "BLKFLSBUF not supported"); #endif /* BLKFLSBUF */ } if (swapfs) { if (cflag || bad_blocks_file) { fprintf(stderr, "Incompatible options not " "allowed when byte-swapping.\n"); exit(FSCK_ERROR); } } return 0; } static const char *my_ver_string = E2FSPROGS_VERSION; static const char *my_ver_date = E2FSPROGS_DATE; int main (int argc, char *argv[]) { errcode_t retval = 0; int exit_value = FSCK_OK; int i; ext2_filsys fs = 0; io_manager io_ptr; struct ext2fs_sb *s; const char *lib_ver_date; int my_ver, lib_ver; e2fsck_t ctx; struct problem_context pctx; int flags, run_result; clear_problem_context(&pctx); #ifdef MTRACE mtrace(); #endif #ifdef MCHECK mcheck(0); #endif my_ver = ext2fs_parse_version_string(my_ver_string); lib_ver = ext2fs_get_library_version(0, &lib_ver_date); if (my_ver > lib_ver) { fprintf( stderr, "Error: ext2fs library version " "out of date!\n"); show_version_only++; } retval = PRS(argc, argv, &ctx); if (retval) { com_err("e2fsck", retval, "while trying to initialize program"); exit(1); } reserve_stdio_fds(); #ifdef RESOURCE_TRACK init_resource_track(&ctx->global_rtrack); #endif if (!(ctx->options & E2F_OPT_PREEN) || show_version_only) fprintf (stderr, "e2fsck %s, %s for EXT2 FS %s, %s\n", my_ver_string, my_ver_date, EXT2FS_VERSION, EXT2FS_DATE); if (show_version_only) { fprintf(stderr, "\tUsing %s, %s\n", error_message(EXT2_ET_BASE), lib_ver_date); exit(0); } check_mount(ctx); if (!(ctx->options & E2F_OPT_PREEN) && !(ctx->options & E2F_OPT_NO) && !(ctx->options & E2F_OPT_YES)) { if (!isatty (0) || !isatty (1)) fatal_error(ctx, "need terminal for interactive repairs"); } ctx->superblock = ctx->use_superblock; restart: #if 1 io_ptr = unix_io_manager; #else io_ptr = test_io_manager; test_io_backing_manager = unix_io_manager; #endif flags = (ctx->options & E2F_OPT_READONLY) ? 0 : EXT2_FLAG_RW; if (ctx->superblock && blocksize) { retval = ext2fs_open(ctx->filesystem_name, flags, ctx->superblock, blocksize, io_ptr, &fs); } else if (ctx->superblock) { for (i=0; possible_block_sizes[i]; i++) { retval = ext2fs_open(ctx->filesystem_name, flags, ctx->superblock, possible_block_sizes[i], io_ptr, &fs); if (!retval) break; } } else retval = ext2fs_open(ctx->filesystem_name, flags, 0, 0, io_ptr, &fs); if (!ctx->superblock && !(ctx->options & E2F_OPT_PREEN) && ((retval == EXT2_ET_BAD_MAGIC) || ((retval == 0) && ext2fs_check_desc(fs)))) { if (!fs || (fs->group_desc_count > 1)) { printf("%s trying backup blocks...\n", retval ? "Couldn't find ext2 superblock," : "Group descriptors look bad..."); ctx->superblock = get_backup_sb(fs); if (fs) ext2fs_close(fs); goto restart; } } if (retval) { com_err(ctx->program_name, retval, "while trying to open %s", ctx->filesystem_name); if (retval == EXT2_ET_REV_TOO_HIGH) { printf("The filesystem revision is apparently " "too high for this version of e2fsck.\n" "(Or the filesystem superblock " "is corrupt)\n\n"); fix_problem(ctx, PR_0_SB_CORRUPT, &pctx); } else if (retval == EXT2_ET_SHORT_READ) printf("Could this be a zero-length partition?\n"); else if ((retval == EPERM) || (retval == EACCES)) printf("You must have %s access to the " "filesystem or be root\n", (ctx->options & E2F_OPT_READONLY) ? "r/o" : "r/w"); else if (retval == ENXIO) printf("Possibly non-existent or swap device?\n"); #ifdef EROFS else if (retval == EROFS) printf("Disk write-protected; use the -n option" "to do a read-only\n" "check of the device.\n"); #endif else fix_problem(ctx, PR_0_SB_CORRUPT, &pctx); exit(FSCK_ERROR); } ctx->fs = fs; fs->priv_data = ctx; #ifdef EXT2_CURRENT_REV if (fs->super->s_rev_level > E2FSCK_CURRENT_REV) { com_err(ctx->program_name, EXT2_ET_REV_TOO_HIGH, "while trying to open %s", ctx->filesystem_name); get_newer: fatal_error(ctx, "Get a newer version of e2fsck!"); } #endif /* * Check for compatibility with the feature sets. We need to * be more stringent than ext2fs_open(). */ s = (struct ext2fs_sb *) fs->super; if ((s->s_feature_compat & ~EXT2_LIB_FEATURE_COMPAT_SUPP) || (s->s_feature_incompat & ~EXT2_LIB_FEATURE_INCOMPAT_SUPP)) { com_err(ctx->program_name, EXT2_ET_UNSUPP_FEATURE, "(%s)", ctx->filesystem_name); goto get_newer; } if (s->s_feature_ro_compat & ~EXT2_LIB_FEATURE_RO_COMPAT_SUPP) { com_err(ctx->program_name, EXT2_ET_RO_UNSUPP_FEATURE, "(%s)", ctx->filesystem_name); goto get_newer; } /* * If the user specified a specific superblock, presumably the * master superblock has been trashed. So we mark the * superblock as dirty, so it can be written out. */ if (ctx->superblock && !(ctx->options & E2F_OPT_READONLY)) ext2fs_mark_super_dirty(fs); /* * Don't overwrite the backup superblock and block * descriptors, until we're sure the filesystem is OK.... */ fs->flags |= EXT2_FLAG_MASTER_SB_ONLY; ehandler_init(fs->io); if (ctx->superblock) set_latch_flags(PR_LATCH_RELOC, PRL_LATCHED, 0); check_super_block(ctx); if (ctx->flags & E2F_FLAG_SIGNAL_MASK) exit(FSCK_ERROR); check_if_skip(ctx); if (bad_blocks_file) read_bad_blocks_file(ctx, bad_blocks_file, replace_bad_blocks); else if (cflag) test_disk(ctx); if (ctx->flags & E2F_FLAG_SIGNAL_MASK) exit(FSCK_ERROR); if (normalize_swapfs) { if ((fs->flags & EXT2_FLAG_SWAP_BYTES) == ext2fs_native_flag()) { fprintf(stderr, "%s: Filesystem byte order " "already normalized.\n", ctx->device_name); exit(FSCK_ERROR); } } if (swapfs) { swap_filesys(ctx); if (ctx->flags & E2F_FLAG_SIGNAL_MASK) exit(FSCK_ERROR); } /* * Mark the system as valid, 'til proven otherwise */ ext2fs_mark_valid(fs); retval = ext2fs_read_bb_inode(fs, &fs->badblocks); if (retval) { com_err(ctx->program_name, retval, "while reading bad blocks inode"); preenhalt(ctx); printf("This doesn't bode well, but we'll try to go on...\n"); } run_result = e2fsck_run(ctx); if (run_result == E2F_FLAG_RESTART) { ext2fs_close(fs); printf("Restarting e2fsck from the beginning...\n"); retval = e2fsck_reset_context(ctx); if (retval) { com_err(ctx->program_name, retval, "while resetting context"); exit(1); } goto restart; } if (run_result & E2F_FLAG_SIGNAL_MASK) exit(FSCK_ERROR); if (run_result & E2F_FLAG_CANCEL) ext2fs_unmark_valid(fs); #ifdef MTRACE mtrace_print("Cleanup"); #endif if (ext2fs_test_changed(fs)) { exit_value = FSCK_NONDESTRUCT; if (!(ctx->options & E2F_OPT_PREEN)) printf("\n%s: ***** FILE SYSTEM WAS MODIFIED *****\n", ctx->device_name); if (root_filesystem && !read_only_root) { printf("%s: ***** REBOOT LINUX *****\n", ctx->device_name); exit_value = FSCK_REBOOT; } } if (ext2fs_test_valid(fs)) fs->flags &= ~EXT2_FLAG_MASTER_SB_ONLY; else exit_value = FSCK_UNCORRECTED; if (!(ctx->options & E2F_OPT_READONLY)) { if (ext2fs_test_valid(fs)) { if (!(fs->super->s_state & EXT2_VALID_FS)) exit_value = FSCK_NONDESTRUCT; fs->super->s_state = EXT2_VALID_FS; } else fs->super->s_state &= ~EXT2_VALID_FS; fs->super->s_mnt_count = 0; fs->super->s_lastcheck = time(NULL); ext2fs_mark_super_dirty(fs); } show_stats(ctx); e2fsck_write_bitmaps(ctx); #ifdef RESOURCE_TRACK if (ctx->options & E2F_OPT_TIME) print_resource_track(NULL, &ctx->global_rtrack); #endif e2fsck_free_context(ctx); ext2fs_close(fs); return exit_value; }