| /* vi: set sw=4 ts=4: */ |
| /* |
| * e2fsck |
| * |
| * Copyright (C) 1993, 1994, 1995, 1996, 1997 Theodore Ts'o. |
| * Copyright (C) 2006 Garrett Kajmowicz |
| * |
| * Dictionary Abstract Data Type |
| * Copyright (C) 1997 Kaz Kylheku <kaz@ashi.footprints.net> |
| * Free Software License: |
| * All rights are reserved by the author, with the following exceptions: |
| * Permission is granted to freely reproduce and distribute this software, |
| * possibly in exchange for a fee, provided that this copyright notice appears |
| * intact. Permission is also granted to adapt this software to produce |
| * derivative works, as long as the modified versions carry this copyright |
| * notice and additional notices stating that the work has been modified. |
| * This source code may be translated into executable form and incorporated |
| * into proprietary software; there is no requirement for such software to |
| * contain a copyright notice related to this source. |
| * |
| * linux/fs/recovery and linux/fs/revoke |
| * Written by Stephen C. Tweedie <sct@redhat.com>, 1999 |
| * |
| * Copyright 1999-2000 Red Hat Software --- All Rights Reserved |
| * |
| * Journal recovery routines for the generic filesystem journaling code; |
| * part of the ext2fs journaling system. |
| * |
| * Licensed under GPLv2 or later, see file LICENSE in this source tree. |
| */ |
| |
| /* |
| //usage:#define e2fsck_trivial_usage |
| //usage: "[-panyrcdfvstDFSV] [-b superblock] [-B blocksize] " |
| //usage: "[-I inode_buffer_blocks] [-P process_inode_size] " |
| //usage: "[-l|-L bad_blocks_file] [-C fd] [-j external_journal] " |
| //usage: "[-E extended-options] device" |
| //usage:#define e2fsck_full_usage "\n\n" |
| //usage: "Check ext2/ext3 file system\n" |
| //usage: "\n -p Automatic repair (no questions)" |
| //usage: "\n -n Make no changes to the filesystem" |
| //usage: "\n -y Assume 'yes' to all questions" |
| //usage: "\n -c Check for bad blocks and add them to the badblock list" |
| //usage: "\n -f Force checking even if filesystem is marked clean" |
| //usage: "\n -v Verbose" |
| //usage: "\n -b superblock Use alternative superblock" |
| //usage: "\n -B blocksize Force blocksize when looking for superblock" |
| //usage: "\n -j journal Set location of the external journal" |
| //usage: "\n -l file Add to badblocks list" |
| //usage: "\n -L file Set badblocks list" |
| */ |
| |
| #include "e2fsck.h" /*Put all of our defines here to clean things up*/ |
| |
| #define _(x) x |
| #define N_(x) x |
| |
| /* |
| * Procedure declarations |
| */ |
| |
| static void e2fsck_pass1_dupblocks(e2fsck_t ctx, char *block_buf); |
| |
| /* pass1.c */ |
| static void e2fsck_use_inode_shortcuts(e2fsck_t ctx, int bool); |
| |
| /* pass2.c */ |
| static int e2fsck_process_bad_inode(e2fsck_t ctx, ext2_ino_t dir, |
| ext2_ino_t ino, char *buf); |
| |
| /* pass3.c */ |
| static int e2fsck_reconnect_file(e2fsck_t ctx, ext2_ino_t inode); |
| static errcode_t e2fsck_expand_directory(e2fsck_t ctx, ext2_ino_t dir, |
| int num, int gauranteed_size); |
| static ext2_ino_t e2fsck_get_lost_and_found(e2fsck_t ctx, int fix); |
| static errcode_t e2fsck_adjust_inode_count(e2fsck_t ctx, ext2_ino_t ino, |
| int adj); |
| |
| /* rehash.c */ |
| static void e2fsck_rehash_directories(e2fsck_t ctx); |
| |
| /* util.c */ |
| static void *e2fsck_allocate_memory(e2fsck_t ctx, unsigned int size, |
| const char *description); |
| static int ask(e2fsck_t ctx, const char * string, int def); |
| static void e2fsck_read_bitmaps(e2fsck_t ctx); |
| static void preenhalt(e2fsck_t ctx); |
| static void e2fsck_read_inode(e2fsck_t ctx, unsigned long ino, |
| struct ext2_inode * inode, const char * proc); |
| static void e2fsck_write_inode(e2fsck_t ctx, unsigned long ino, |
| struct ext2_inode * inode, const char * proc); |
| static blk_t get_backup_sb(e2fsck_t ctx, ext2_filsys fs, |
| const char *name, io_manager manager); |
| |
| /* unix.c */ |
| static void e2fsck_clear_progbar(e2fsck_t ctx); |
| static int e2fsck_simple_progress(e2fsck_t ctx, const char *label, |
| float percent, unsigned int dpynum); |
| |
| |
| /* |
| * problem.h --- e2fsck problem error codes |
| */ |
| |
| typedef __u32 problem_t; |
| |
| struct problem_context { |
| errcode_t errcode; |
| ext2_ino_t ino, ino2, dir; |
| struct ext2_inode *inode; |
| struct ext2_dir_entry *dirent; |
| blk_t blk, blk2; |
| e2_blkcnt_t blkcount; |
| int group; |
| __u64 num; |
| const char *str; |
| }; |
| |
| |
| /* |
| * Function declarations |
| */ |
| static int fix_problem(e2fsck_t ctx, problem_t code, struct problem_context *pctx); |
| static int end_problem_latch(e2fsck_t ctx, int mask); |
| static int set_latch_flags(int mask, int setflags, int clearflags); |
| static void clear_problem_context(struct problem_context *ctx); |
| |
| /* |
| * Dictionary Abstract Data Type |
| * Copyright (C) 1997 Kaz Kylheku <kaz@ashi.footprints.net> |
| * |
| * dict.h v 1.22.2.6 2000/11/13 01:36:44 kaz |
| * kazlib_1_20 |
| */ |
| |
| #ifndef DICT_H |
| #define DICT_H |
| |
| /* |
| * Blurb for inclusion into C++ translation units |
| */ |
| |
| typedef unsigned long dictcount_t; |
| #define DICTCOUNT_T_MAX ULONG_MAX |
| |
| /* |
| * The dictionary is implemented as a red-black tree |
| */ |
| |
| typedef enum { dnode_red, dnode_black } dnode_color_t; |
| |
| typedef struct dnode_t { |
| struct dnode_t *dict_left; |
| struct dnode_t *dict_right; |
| struct dnode_t *dict_parent; |
| dnode_color_t dict_color; |
| const void *dict_key; |
| void *dict_data; |
| } dnode_t; |
| |
| typedef int (*dict_comp_t)(const void *, const void *); |
| typedef void (*dnode_free_t)(dnode_t *); |
| |
| typedef struct dict_t { |
| dnode_t dict_nilnode; |
| dictcount_t dict_nodecount; |
| dictcount_t dict_maxcount; |
| dict_comp_t dict_compare; |
| dnode_free_t dict_freenode; |
| int dict_dupes; |
| } dict_t; |
| |
| typedef void (*dnode_process_t)(dict_t *, dnode_t *, void *); |
| |
| typedef struct dict_load_t { |
| dict_t *dict_dictptr; |
| dnode_t dict_nilnode; |
| } dict_load_t; |
| |
| #define dict_count(D) ((D)->dict_nodecount) |
| #define dnode_get(N) ((N)->dict_data) |
| #define dnode_getkey(N) ((N)->dict_key) |
| |
| #endif |
| |
| /* |
| * Compatibility header file for e2fsck which should be included |
| * instead of linux/jfs.h |
| * |
| * Copyright (C) 2000 Stephen C. Tweedie |
| */ |
| |
| /* |
| * Pull in the definition of the e2fsck context structure |
| */ |
| |
| struct buffer_head { |
| char b_data[8192]; |
| e2fsck_t b_ctx; |
| io_channel b_io; |
| int b_size; |
| blk_t b_blocknr; |
| int b_dirty; |
| int b_uptodate; |
| int b_err; |
| }; |
| |
| |
| #define K_DEV_FS 1 |
| #define K_DEV_JOURNAL 2 |
| |
| #define lock_buffer(bh) do {} while (0) |
| #define unlock_buffer(bh) do {} while (0) |
| #define buffer_req(bh) 1 |
| #define do_readahead(journal, start) do {} while (0) |
| |
| static e2fsck_t e2fsck_global_ctx; /* Try your very best not to use this! */ |
| |
| typedef struct { |
| int object_length; |
| } kmem_cache_t; |
| |
| #define kmem_cache_alloc(cache,flags) malloc((cache)->object_length) |
| |
| /* |
| * We use the standard libext2fs portability tricks for inline |
| * functions. |
| */ |
| |
| static kmem_cache_t * do_cache_create(int len) |
| { |
| kmem_cache_t *new_cache; |
| |
| new_cache = xmalloc(sizeof(*new_cache)); |
| new_cache->object_length = len; |
| return new_cache; |
| } |
| |
| static void do_cache_destroy(kmem_cache_t *cache) |
| { |
| free(cache); |
| } |
| |
| |
| /* |
| * Dictionary Abstract Data Type |
| */ |
| |
| |
| /* |
| * These macros provide short convenient names for structure members, |
| * which are embellished with dict_ prefixes so that they are |
| * properly confined to the documented namespace. It's legal for a |
| * program which uses dict to define, for instance, a macro called ``parent''. |
| * Such a macro would interfere with the dnode_t struct definition. |
| * In general, highly portable and reusable C modules which expose their |
| * structures need to confine structure member names to well-defined spaces. |
| * The resulting identifiers aren't necessarily convenient to use, nor |
| * readable, in the implementation, however! |
| */ |
| |
| #define left dict_left |
| #define right dict_right |
| #define parent dict_parent |
| #define color dict_color |
| #define key dict_key |
| #define data dict_data |
| |
| #define nilnode dict_nilnode |
| #define maxcount dict_maxcount |
| #define compare dict_compare |
| #define dupes dict_dupes |
| |
| #define dict_root(D) ((D)->nilnode.left) |
| #define dict_nil(D) (&(D)->nilnode) |
| |
| static void dnode_free(dnode_t *node); |
| |
| /* |
| * Perform a ``left rotation'' adjustment on the tree. The given node P and |
| * its right child C are rearranged so that the P instead becomes the left |
| * child of C. The left subtree of C is inherited as the new right subtree |
| * for P. The ordering of the keys within the tree is thus preserved. |
| */ |
| |
| static void rotate_left(dnode_t *upper) |
| { |
| dnode_t *lower, *lowleft, *upparent; |
| |
| lower = upper->right; |
| upper->right = lowleft = lower->left; |
| lowleft->parent = upper; |
| |
| lower->parent = upparent = upper->parent; |
| |
| /* don't need to check for root node here because root->parent is |
| the sentinel nil node, and root->parent->left points back to root */ |
| |
| if (upper == upparent->left) { |
| upparent->left = lower; |
| } else { |
| assert (upper == upparent->right); |
| upparent->right = lower; |
| } |
| |
| lower->left = upper; |
| upper->parent = lower; |
| } |
| |
| /* |
| * This operation is the ``mirror'' image of rotate_left. It is |
| * the same procedure, but with left and right interchanged. |
| */ |
| |
| static void rotate_right(dnode_t *upper) |
| { |
| dnode_t *lower, *lowright, *upparent; |
| |
| lower = upper->left; |
| upper->left = lowright = lower->right; |
| lowright->parent = upper; |
| |
| lower->parent = upparent = upper->parent; |
| |
| if (upper == upparent->right) { |
| upparent->right = lower; |
| } else { |
| assert (upper == upparent->left); |
| upparent->left = lower; |
| } |
| |
| lower->right = upper; |
| upper->parent = lower; |
| } |
| |
| /* |
| * Do a postorder traversal of the tree rooted at the specified |
| * node and free everything under it. Used by dict_free(). |
| */ |
| |
| static void free_nodes(dict_t *dict, dnode_t *node, dnode_t *nil) |
| { |
| if (node == nil) |
| return; |
| free_nodes(dict, node->left, nil); |
| free_nodes(dict, node->right, nil); |
| dict->dict_freenode(node); |
| } |
| |
| /* |
| * Verify that the tree contains the given node. This is done by |
| * traversing all of the nodes and comparing their pointers to the |
| * given pointer. Returns 1 if the node is found, otherwise |
| * returns zero. It is intended for debugging purposes. |
| */ |
| |
| static int verify_dict_has_node(dnode_t *nil, dnode_t *root, dnode_t *node) |
| { |
| if (root != nil) { |
| return root == node |
| || verify_dict_has_node(nil, root->left, node) |
| || verify_dict_has_node(nil, root->right, node); |
| } |
| return 0; |
| } |
| |
| |
| /* |
| * Select a different set of node allocator routines. |
| */ |
| |
| static void dict_set_allocator(dict_t *dict, dnode_free_t fr) |
| { |
| assert(dict_count(dict) == 0); |
| dict->dict_freenode = fr; |
| } |
| |
| /* |
| * Free all the nodes in the dictionary by using the dictionary's |
| * installed free routine. The dictionary is emptied. |
| */ |
| |
| static void dict_free_nodes(dict_t *dict) |
| { |
| dnode_t *nil = dict_nil(dict), *root = dict_root(dict); |
| free_nodes(dict, root, nil); |
| dict->dict_nodecount = 0; |
| dict->nilnode.left = &dict->nilnode; |
| dict->nilnode.right = &dict->nilnode; |
| } |
| |
| /* |
| * Initialize a user-supplied dictionary object. |
| */ |
| |
| static dict_t *dict_init(dict_t *dict, dictcount_t maxcount, dict_comp_t comp) |
| { |
| dict->compare = comp; |
| dict->dict_freenode = dnode_free; |
| dict->dict_nodecount = 0; |
| dict->maxcount = maxcount; |
| dict->nilnode.left = &dict->nilnode; |
| dict->nilnode.right = &dict->nilnode; |
| dict->nilnode.parent = &dict->nilnode; |
| dict->nilnode.color = dnode_black; |
| dict->dupes = 0; |
| return dict; |
| } |
| |
| /* |
| * Locate a node in the dictionary having the given key. |
| * If the node is not found, a null a pointer is returned (rather than |
| * a pointer that dictionary's nil sentinel node), otherwise a pointer to the |
| * located node is returned. |
| */ |
| |
| static dnode_t *dict_lookup(dict_t *dict, const void *key) |
| { |
| dnode_t *root = dict_root(dict); |
| dnode_t *nil = dict_nil(dict); |
| dnode_t *saved; |
| int result; |
| |
| /* simple binary search adapted for trees that contain duplicate keys */ |
| |
| while (root != nil) { |
| result = dict->compare(key, root->key); |
| if (result < 0) |
| root = root->left; |
| else if (result > 0) |
| root = root->right; |
| else { |
| if (!dict->dupes) { /* no duplicates, return match */ |
| return root; |
| } else { /* could be dupes, find leftmost one */ |
| do { |
| saved = root; |
| root = root->left; |
| while (root != nil && dict->compare(key, root->key)) |
| root = root->right; |
| } while (root != nil); |
| return saved; |
| } |
| } |
| } |
| |
| return NULL; |
| } |
| |
| /* |
| * Insert a node into the dictionary. The node should have been |
| * initialized with a data field. All other fields are ignored. |
| * The behavior is undefined if the user attempts to insert into |
| * a dictionary that is already full (for which the dict_isfull() |
| * function returns true). |
| */ |
| |
| static void dict_insert(dict_t *dict, dnode_t *node, const void *key) |
| { |
| dnode_t *where = dict_root(dict), *nil = dict_nil(dict); |
| dnode_t *parent = nil, *uncle, *grandpa; |
| int result = -1; |
| |
| node->key = key; |
| |
| /* basic binary tree insert */ |
| |
| while (where != nil) { |
| parent = where; |
| result = dict->compare(key, where->key); |
| /* trap attempts at duplicate key insertion unless it's explicitly allowed */ |
| assert(dict->dupes || result != 0); |
| if (result < 0) |
| where = where->left; |
| else |
| where = where->right; |
| } |
| |
| assert(where == nil); |
| |
| if (result < 0) |
| parent->left = node; |
| else |
| parent->right = node; |
| |
| node->parent = parent; |
| node->left = nil; |
| node->right = nil; |
| |
| dict->dict_nodecount++; |
| |
| /* red black adjustments */ |
| |
| node->color = dnode_red; |
| |
| while (parent->color == dnode_red) { |
| grandpa = parent->parent; |
| if (parent == grandpa->left) { |
| uncle = grandpa->right; |
| if (uncle->color == dnode_red) { /* red parent, red uncle */ |
| parent->color = dnode_black; |
| uncle->color = dnode_black; |
| grandpa->color = dnode_red; |
| node = grandpa; |
| parent = grandpa->parent; |
| } else { /* red parent, black uncle */ |
| if (node == parent->right) { |
| rotate_left(parent); |
| parent = node; |
| assert (grandpa == parent->parent); |
| /* rotation between parent and child preserves grandpa */ |
| } |
| parent->color = dnode_black; |
| grandpa->color = dnode_red; |
| rotate_right(grandpa); |
| break; |
| } |
| } else { /* symmetric cases: parent == parent->parent->right */ |
| uncle = grandpa->left; |
| if (uncle->color == dnode_red) { |
| parent->color = dnode_black; |
| uncle->color = dnode_black; |
| grandpa->color = dnode_red; |
| node = grandpa; |
| parent = grandpa->parent; |
| } else { |
| if (node == parent->left) { |
| rotate_right(parent); |
| parent = node; |
| assert (grandpa == parent->parent); |
| } |
| parent->color = dnode_black; |
| grandpa->color = dnode_red; |
| rotate_left(grandpa); |
| break; |
| } |
| } |
| } |
| |
| dict_root(dict)->color = dnode_black; |
| } |
| |
| /* |
| * Allocate a node using the dictionary's allocator routine, give it |
| * the data item. |
| */ |
| |
| static dnode_t *dnode_init(dnode_t *dnode, void *data) |
| { |
| dnode->data = data; |
| dnode->parent = NULL; |
| dnode->left = NULL; |
| dnode->right = NULL; |
| return dnode; |
| } |
| |
| static int dict_alloc_insert(dict_t *dict, const void *key, void *data) |
| { |
| dnode_t *node = xmalloc(sizeof(dnode_t)); |
| |
| dnode_init(node, data); |
| dict_insert(dict, node, key); |
| return 1; |
| } |
| |
| /* |
| * Return the node with the lowest (leftmost) key. If the dictionary is empty |
| * (that is, dict_isempty(dict) returns 1) a null pointer is returned. |
| */ |
| |
| static dnode_t *dict_first(dict_t *dict) |
| { |
| dnode_t *nil = dict_nil(dict), *root = dict_root(dict), *left; |
| |
| if (root != nil) |
| while ((left = root->left) != nil) |
| root = left; |
| |
| return (root == nil) ? NULL : root; |
| } |
| |
| /* |
| * Return the given node's successor node---the node which has the |
| * next key in the left to right ordering. If the node has |
| * no successor, a null pointer is returned rather than a pointer to |
| * the nil node. |
| */ |
| |
| static dnode_t *dict_next(dict_t *dict, dnode_t *curr) |
| { |
| dnode_t *nil = dict_nil(dict), *parent, *left; |
| |
| if (curr->right != nil) { |
| curr = curr->right; |
| while ((left = curr->left) != nil) |
| curr = left; |
| return curr; |
| } |
| |
| parent = curr->parent; |
| |
| while (parent != nil && curr == parent->right) { |
| curr = parent; |
| parent = curr->parent; |
| } |
| |
| return (parent == nil) ? NULL : parent; |
| } |
| |
| |
| static void dnode_free(dnode_t *node) |
| { |
| free(node); |
| } |
| |
| |
| #undef left |
| #undef right |
| #undef parent |
| #undef color |
| #undef key |
| #undef data |
| |
| #undef nilnode |
| #undef maxcount |
| #undef compare |
| #undef dupes |
| |
| |
| /* |
| * dirinfo.c --- maintains the directory information table for e2fsck. |
| */ |
| |
| /* |
| * This subroutine is called during pass1 to create a directory info |
| * entry. During pass1, the passed-in parent is 0; it will get filled |
| * in during pass2. |
| */ |
| static void e2fsck_add_dir_info(e2fsck_t ctx, ext2_ino_t ino, ext2_ino_t parent) |
| { |
| struct dir_info *dir; |
| int i, j; |
| ext2_ino_t num_dirs; |
| errcode_t retval; |
| unsigned long old_size; |
| |
| if (!ctx->dir_info) { |
| ctx->dir_info_count = 0; |
| retval = ext2fs_get_num_dirs(ctx->fs, &num_dirs); |
| if (retval) |
| num_dirs = 1024; /* Guess */ |
| ctx->dir_info_size = num_dirs + 10; |
| ctx->dir_info = (struct dir_info *) |
| e2fsck_allocate_memory(ctx, ctx->dir_info_size |
| * sizeof (struct dir_info), |
| "directory map"); |
| } |
| |
| if (ctx->dir_info_count >= ctx->dir_info_size) { |
| old_size = ctx->dir_info_size * sizeof(struct dir_info); |
| ctx->dir_info_size += 10; |
| retval = ext2fs_resize_mem(old_size, ctx->dir_info_size * |
| sizeof(struct dir_info), |
| &ctx->dir_info); |
| if (retval) { |
| ctx->dir_info_size -= 10; |
| return; |
| } |
| } |
| |
| /* |
| * Normally, add_dir_info is called with each inode in |
| * sequential order; but once in a while (like when pass 3 |
| * needs to recreate the root directory or lost+found |
| * directory) it is called out of order. In those cases, we |
| * need to move the dir_info entries down to make room, since |
| * the dir_info array needs to be sorted by inode number for |
| * get_dir_info()'s sake. |
| */ |
| if (ctx->dir_info_count && |
| ctx->dir_info[ctx->dir_info_count-1].ino >= ino) { |
| for (i = ctx->dir_info_count-1; i > 0; i--) |
| if (ctx->dir_info[i-1].ino < ino) |
| break; |
| dir = &ctx->dir_info[i]; |
| if (dir->ino != ino) |
| for (j = ctx->dir_info_count++; j > i; j--) |
| ctx->dir_info[j] = ctx->dir_info[j-1]; |
| } else |
| dir = &ctx->dir_info[ctx->dir_info_count++]; |
| |
| dir->ino = ino; |
| dir->dotdot = parent; |
| dir->parent = parent; |
| } |
| |
| /* |
| * get_dir_info() --- given an inode number, try to find the directory |
| * information entry for it. |
| */ |
| static struct dir_info *e2fsck_get_dir_info(e2fsck_t ctx, ext2_ino_t ino) |
| { |
| int low, high, mid; |
| |
| low = 0; |
| high = ctx->dir_info_count-1; |
| if (!ctx->dir_info) |
| return 0; |
| if (ino == ctx->dir_info[low].ino) |
| return &ctx->dir_info[low]; |
| if (ino == ctx->dir_info[high].ino) |
| return &ctx->dir_info[high]; |
| |
| while (low < high) { |
| mid = (low+high)/2; |
| if (mid == low || mid == high) |
| break; |
| if (ino == ctx->dir_info[mid].ino) |
| return &ctx->dir_info[mid]; |
| if (ino < ctx->dir_info[mid].ino) |
| high = mid; |
| else |
| low = mid; |
| } |
| return 0; |
| } |
| |
| /* |
| * Free the dir_info structure when it isn't needed any more. |
| */ |
| static void e2fsck_free_dir_info(e2fsck_t ctx) |
| { |
| ext2fs_free_mem(&ctx->dir_info); |
| ctx->dir_info_size = 0; |
| ctx->dir_info_count = 0; |
| } |
| |
| /* |
| * Return the count of number of directories in the dir_info structure |
| */ |
| static int e2fsck_get_num_dirinfo(e2fsck_t ctx) |
| { |
| return ctx->dir_info_count; |
| } |
| |
| /* |
| * A simple interator function |
| */ |
| static struct dir_info *e2fsck_dir_info_iter(e2fsck_t ctx, int *control) |
| { |
| if (*control >= ctx->dir_info_count) |
| return 0; |
| |
| return ctx->dir_info + (*control)++; |
| } |
| |
| /* |
| * dirinfo.c --- maintains the directory information table for e2fsck. |
| * |
| */ |
| |
| #ifdef ENABLE_HTREE |
| |
| /* |
| * This subroutine is called during pass1 to create a directory info |
| * entry. During pass1, the passed-in parent is 0; it will get filled |
| * in during pass2. |
| */ |
| static void e2fsck_add_dx_dir(e2fsck_t ctx, ext2_ino_t ino, int num_blocks) |
| { |
| struct dx_dir_info *dir; |
| int i, j; |
| errcode_t retval; |
| unsigned long old_size; |
| |
| if (!ctx->dx_dir_info) { |
| ctx->dx_dir_info_count = 0; |
| ctx->dx_dir_info_size = 100; /* Guess */ |
| ctx->dx_dir_info = (struct dx_dir_info *) |
| e2fsck_allocate_memory(ctx, ctx->dx_dir_info_size |
| * sizeof (struct dx_dir_info), |
| "directory map"); |
| } |
| |
| if (ctx->dx_dir_info_count >= ctx->dx_dir_info_size) { |
| old_size = ctx->dx_dir_info_size * sizeof(struct dx_dir_info); |
| ctx->dx_dir_info_size += 10; |
| retval = ext2fs_resize_mem(old_size, ctx->dx_dir_info_size * |
| sizeof(struct dx_dir_info), |
| &ctx->dx_dir_info); |
| if (retval) { |
| ctx->dx_dir_info_size -= 10; |
| return; |
| } |
| } |
| |
| /* |
| * Normally, add_dx_dir_info is called with each inode in |
| * sequential order; but once in a while (like when pass 3 |
| * needs to recreate the root directory or lost+found |
| * directory) it is called out of order. In those cases, we |
| * need to move the dx_dir_info entries down to make room, since |
| * the dx_dir_info array needs to be sorted by inode number for |
| * get_dx_dir_info()'s sake. |
| */ |
| if (ctx->dx_dir_info_count && |
| ctx->dx_dir_info[ctx->dx_dir_info_count-1].ino >= ino) { |
| for (i = ctx->dx_dir_info_count-1; i > 0; i--) |
| if (ctx->dx_dir_info[i-1].ino < ino) |
| break; |
| dir = &ctx->dx_dir_info[i]; |
| if (dir->ino != ino) |
| for (j = ctx->dx_dir_info_count++; j > i; j--) |
| ctx->dx_dir_info[j] = ctx->dx_dir_info[j-1]; |
| } else |
| dir = &ctx->dx_dir_info[ctx->dx_dir_info_count++]; |
| |
| dir->ino = ino; |
| dir->numblocks = num_blocks; |
| dir->hashversion = 0; |
| dir->dx_block = e2fsck_allocate_memory(ctx, num_blocks |
| * sizeof (struct dx_dirblock_info), |
| "dx_block info array"); |
| } |
| |
| /* |
| * get_dx_dir_info() --- given an inode number, try to find the directory |
| * information entry for it. |
| */ |
| static struct dx_dir_info *e2fsck_get_dx_dir_info(e2fsck_t ctx, ext2_ino_t ino) |
| { |
| int low, high, mid; |
| |
| low = 0; |
| high = ctx->dx_dir_info_count-1; |
| if (!ctx->dx_dir_info) |
| return 0; |
| if (ino == ctx->dx_dir_info[low].ino) |
| return &ctx->dx_dir_info[low]; |
| if (ino == ctx->dx_dir_info[high].ino) |
| return &ctx->dx_dir_info[high]; |
| |
| while (low < high) { |
| mid = (low+high)/2; |
| if (mid == low || mid == high) |
| break; |
| if (ino == ctx->dx_dir_info[mid].ino) |
| return &ctx->dx_dir_info[mid]; |
| if (ino < ctx->dx_dir_info[mid].ino) |
| high = mid; |
| else |
| low = mid; |
| } |
| return 0; |
| } |
| |
| /* |
| * Free the dx_dir_info structure when it isn't needed any more. |
| */ |
| static void e2fsck_free_dx_dir_info(e2fsck_t ctx) |
| { |
| int i; |
| struct dx_dir_info *dir; |
| |
| if (ctx->dx_dir_info) { |
| dir = ctx->dx_dir_info; |
| for (i=0; i < ctx->dx_dir_info_count; i++) { |
| ext2fs_free_mem(&dir->dx_block); |
| } |
| ext2fs_free_mem(&ctx->dx_dir_info); |
| } |
| ctx->dx_dir_info_size = 0; |
| ctx->dx_dir_info_count = 0; |
| } |
| |
| /* |
| * A simple interator function |
| */ |
| static struct dx_dir_info *e2fsck_dx_dir_info_iter(e2fsck_t ctx, int *control) |
| { |
| if (*control >= ctx->dx_dir_info_count) |
| return 0; |
| |
| return ctx->dx_dir_info + (*control)++; |
| } |
| |
| #endif /* ENABLE_HTREE */ |
| /* |
| * e2fsck.c - a consistency checker for the new extended file system. |
| * |
| */ |
| |
| /* |
| * This function allocates an e2fsck context |
| */ |
| static errcode_t e2fsck_allocate_context(e2fsck_t *ret) |
| { |
| e2fsck_t context; |
| errcode_t retval; |
| |
| retval = ext2fs_get_mem(sizeof(struct e2fsck_struct), &context); |
| if (retval) |
| return retval; |
| |
| memset(context, 0, sizeof(struct e2fsck_struct)); |
| |
| context->process_inode_size = 256; |
| context->ext_attr_ver = 2; |
| |
| *ret = context; |
| return 0; |
| } |
| |
| struct ea_refcount_el { |
| blk_t ea_blk; |
| int ea_count; |
| }; |
| |
| struct ea_refcount { |
| blk_t count; |
| blk_t size; |
| blk_t cursor; |
| struct ea_refcount_el *list; |
| }; |
| |
| static void ea_refcount_free(ext2_refcount_t refcount) |
| { |
| if (!refcount) |
| return; |
| |
| ext2fs_free_mem(&refcount->list); |
| ext2fs_free_mem(&refcount); |
| } |
| |
| /* |
| * This function resets an e2fsck context; it is called when e2fsck |
| * needs to be restarted. |
| */ |
| static errcode_t e2fsck_reset_context(e2fsck_t ctx) |
| { |
| ctx->flags = 0; |
| ctx->lost_and_found = 0; |
| ctx->bad_lost_and_found = 0; |
| ext2fs_free_inode_bitmap(ctx->inode_used_map); |
| ctx->inode_used_map = 0; |
| ext2fs_free_inode_bitmap(ctx->inode_dir_map); |
| ctx->inode_dir_map = 0; |
| ext2fs_free_inode_bitmap(ctx->inode_reg_map); |
| ctx->inode_reg_map = 0; |
| ext2fs_free_block_bitmap(ctx->block_found_map); |
| ctx->block_found_map = 0; |
| ext2fs_free_icount(ctx->inode_link_info); |
| ctx->inode_link_info = 0; |
| if (ctx->journal_io) { |
| if (ctx->fs && ctx->fs->io != ctx->journal_io) |
| io_channel_close(ctx->journal_io); |
| ctx->journal_io = 0; |
| } |
| if (ctx->fs) { |
| ext2fs_free_dblist(ctx->fs->dblist); |
| ctx->fs->dblist = 0; |
| } |
| e2fsck_free_dir_info(ctx); |
| #ifdef ENABLE_HTREE |
| e2fsck_free_dx_dir_info(ctx); |
| #endif |
| ea_refcount_free(ctx->refcount); |
| ctx->refcount = 0; |
| ea_refcount_free(ctx->refcount_extra); |
| ctx->refcount_extra = 0; |
| ext2fs_free_block_bitmap(ctx->block_dup_map); |
| ctx->block_dup_map = 0; |
| ext2fs_free_block_bitmap(ctx->block_ea_map); |
| ctx->block_ea_map = 0; |
| ext2fs_free_inode_bitmap(ctx->inode_bad_map); |
| ctx->inode_bad_map = 0; |
| ext2fs_free_inode_bitmap(ctx->inode_imagic_map); |
| ctx->inode_imagic_map = 0; |
| ext2fs_u32_list_free(ctx->dirs_to_hash); |
| ctx->dirs_to_hash = 0; |
| |
| /* |
| * Clear the array of invalid meta-data flags |
| */ |
| ext2fs_free_mem(&ctx->invalid_inode_bitmap_flag); |
| ext2fs_free_mem(&ctx->invalid_block_bitmap_flag); |
| ext2fs_free_mem(&ctx->invalid_inode_table_flag); |
| |
| /* Clear statistic counters */ |
| ctx->fs_directory_count = 0; |
| ctx->fs_regular_count = 0; |
| ctx->fs_blockdev_count = 0; |
| ctx->fs_chardev_count = 0; |
| ctx->fs_links_count = 0; |
| ctx->fs_symlinks_count = 0; |
| ctx->fs_fast_symlinks_count = 0; |
| ctx->fs_fifo_count = 0; |
| ctx->fs_total_count = 0; |
| ctx->fs_sockets_count = 0; |
| ctx->fs_ind_count = 0; |
| ctx->fs_dind_count = 0; |
| ctx->fs_tind_count = 0; |
| ctx->fs_fragmented = 0; |
| ctx->large_files = 0; |
| |
| /* Reset the superblock to the user's requested value */ |
| ctx->superblock = ctx->use_superblock; |
| |
| return 0; |
| } |
| |
| static void e2fsck_free_context(e2fsck_t ctx) |
| { |
| if (!ctx) |
| return; |
| |
| e2fsck_reset_context(ctx); |
| if (ctx->blkid) |
| blkid_put_cache(ctx->blkid); |
| |
| ext2fs_free_mem(&ctx); |
| } |
| |
| /* |
| * ea_refcount.c |
| */ |
| |
| /* |
| * The strategy we use for keeping track of EA refcounts is as |
| * follows. We keep a sorted array of first EA blocks and its |
| * reference counts. Once the refcount has dropped to zero, it is |
| * removed from the array to save memory space. Once the EA block is |
| * checked, its bit is set in the block_ea_map bitmap. |
| */ |
| |
| |
| static errcode_t ea_refcount_create(int size, ext2_refcount_t *ret) |
| { |
| ext2_refcount_t refcount; |
| errcode_t retval; |
| size_t bytes; |
| |
| retval = ext2fs_get_mem(sizeof(struct ea_refcount), &refcount); |
| if (retval) |
| return retval; |
| memset(refcount, 0, sizeof(struct ea_refcount)); |
| |
| if (!size) |
| size = 500; |
| refcount->size = size; |
| bytes = (size_t) (size * sizeof(struct ea_refcount_el)); |
| #ifdef DEBUG |
| printf("Refcount allocated %d entries, %d bytes.\n", |
| refcount->size, bytes); |
| #endif |
| retval = ext2fs_get_mem(bytes, &refcount->list); |
| if (retval) |
| goto errout; |
| memset(refcount->list, 0, bytes); |
| |
| refcount->count = 0; |
| refcount->cursor = 0; |
| |
| *ret = refcount; |
| return 0; |
| |
| errout: |
| ea_refcount_free(refcount); |
| return retval; |
| } |
| |
| /* |
| * collapse_refcount() --- go through the refcount array, and get rid |
| * of any count == zero entries |
| */ |
| static void refcount_collapse(ext2_refcount_t refcount) |
| { |
| unsigned int i, j; |
| struct ea_refcount_el *list; |
| |
| list = refcount->list; |
| for (i = 0, j = 0; i < refcount->count; i++) { |
| if (list[i].ea_count) { |
| if (i != j) |
| list[j] = list[i]; |
| j++; |
| } |
| } |
| #if defined(DEBUG) || defined(TEST_PROGRAM) |
| printf("Refcount_collapse: size was %d, now %d\n", |
| refcount->count, j); |
| #endif |
| refcount->count = j; |
| } |
| |
| |
| /* |
| * insert_refcount_el() --- Insert a new entry into the sorted list at a |
| * specified position. |
| */ |
| static struct ea_refcount_el *insert_refcount_el(ext2_refcount_t refcount, |
| blk_t blk, int pos) |
| { |
| struct ea_refcount_el *el; |
| errcode_t retval; |
| blk_t new_size = 0; |
| int num; |
| |
| if (refcount->count >= refcount->size) { |
| new_size = refcount->size + 100; |
| #ifdef DEBUG |
| printf("Reallocating refcount %d entries...\n", new_size); |
| #endif |
| retval = ext2fs_resize_mem((size_t) refcount->size * |
| sizeof(struct ea_refcount_el), |
| (size_t) new_size * |
| sizeof(struct ea_refcount_el), |
| &refcount->list); |
| if (retval) |
| return 0; |
| refcount->size = new_size; |
| } |
| num = (int) refcount->count - pos; |
| if (num < 0) |
| return 0; /* should never happen */ |
| if (num) { |
| memmove(&refcount->list[pos+1], &refcount->list[pos], |
| sizeof(struct ea_refcount_el) * num); |
| } |
| refcount->count++; |
| el = &refcount->list[pos]; |
| el->ea_count = 0; |
| el->ea_blk = blk; |
| return el; |
| } |
| |
| |
| /* |
| * get_refcount_el() --- given an block number, try to find refcount |
| * information in the sorted list. If the create flag is set, |
| * and we can't find an entry, create one in the sorted list. |
| */ |
| static struct ea_refcount_el *get_refcount_el(ext2_refcount_t refcount, |
| blk_t blk, int create) |
| { |
| float range; |
| int low, high, mid; |
| blk_t lowval, highval; |
| |
| if (!refcount || !refcount->list) |
| return 0; |
| retry: |
| low = 0; |
| high = (int) refcount->count-1; |
| if (create && ((refcount->count == 0) || |
| (blk > refcount->list[high].ea_blk))) { |
| if (refcount->count >= refcount->size) |
| refcount_collapse(refcount); |
| |
| return insert_refcount_el(refcount, blk, |
| (unsigned) refcount->count); |
| } |
| if (refcount->count == 0) |
| return 0; |
| |
| if (refcount->cursor >= refcount->count) |
| refcount->cursor = 0; |
| if (blk == refcount->list[refcount->cursor].ea_blk) |
| return &refcount->list[refcount->cursor++]; |
| #ifdef DEBUG |
| printf("Non-cursor get_refcount_el: %u\n", blk); |
| #endif |
| while (low <= high) { |
| if (low == high) |
| mid = low; |
| else { |
| /* Interpolate for efficiency */ |
| lowval = refcount->list[low].ea_blk; |
| highval = refcount->list[high].ea_blk; |
| |
| if (blk < lowval) |
| range = 0; |
| else if (blk > highval) |
| range = 1; |
| else |
| range = ((float) (blk - lowval)) / |
| (highval - lowval); |
| mid = low + ((int) (range * (high-low))); |
| } |
| |
| if (blk == refcount->list[mid].ea_blk) { |
| refcount->cursor = mid+1; |
| return &refcount->list[mid]; |
| } |
| if (blk < refcount->list[mid].ea_blk) |
| high = mid-1; |
| else |
| low = mid+1; |
| } |
| /* |
| * If we need to create a new entry, it should be right at |
| * low (where high will be left at low-1). |
| */ |
| if (create) { |
| if (refcount->count >= refcount->size) { |
| refcount_collapse(refcount); |
| if (refcount->count < refcount->size) |
| goto retry; |
| } |
| return insert_refcount_el(refcount, blk, low); |
| } |
| return 0; |
| } |
| |
| static errcode_t |
| ea_refcount_increment(ext2_refcount_t refcount, blk_t blk, int *ret) |
| { |
| struct ea_refcount_el *el; |
| |
| el = get_refcount_el(refcount, blk, 1); |
| if (!el) |
| return EXT2_ET_NO_MEMORY; |
| el->ea_count++; |
| |
| if (ret) |
| *ret = el->ea_count; |
| return 0; |
| } |
| |
| static errcode_t |
| ea_refcount_decrement(ext2_refcount_t refcount, blk_t blk, int *ret) |
| { |
| struct ea_refcount_el *el; |
| |
| el = get_refcount_el(refcount, blk, 0); |
| if (!el || el->ea_count == 0) |
| return EXT2_ET_INVALID_ARGUMENT; |
| |
| el->ea_count--; |
| |
| if (ret) |
| *ret = el->ea_count; |
| return 0; |
| } |
| |
| static errcode_t |
| ea_refcount_store(ext2_refcount_t refcount, blk_t blk, int count) |
| { |
| struct ea_refcount_el *el; |
| |
| /* |
| * Get the refcount element |
| */ |
| el = get_refcount_el(refcount, blk, count ? 1 : 0); |
| if (!el) |
| return count ? EXT2_ET_NO_MEMORY : 0; |
| el->ea_count = count; |
| return 0; |
| } |
| |
| static inline void ea_refcount_intr_begin(ext2_refcount_t refcount) |
| { |
| refcount->cursor = 0; |
| } |
| |
| |
| static blk_t ea_refcount_intr_next(ext2_refcount_t refcount, int *ret) |
| { |
| struct ea_refcount_el *list; |
| |
| while (1) { |
| if (refcount->cursor >= refcount->count) |
| return 0; |
| list = refcount->list; |
| if (list[refcount->cursor].ea_count) { |
| if (ret) |
| *ret = list[refcount->cursor].ea_count; |
| return list[refcount->cursor++].ea_blk; |
| } |
| refcount->cursor++; |
| } |
| } |
| |
| |
| /* |
| * ehandler.c --- handle bad block errors which come up during the |
| * course of an e2fsck session. |
| */ |
| |
| |
| static const char *operation; |
| |
| static errcode_t |
| e2fsck_handle_read_error(io_channel channel, unsigned long block, int count, |
| void *data, size_t size FSCK_ATTR((unused)), |
| int actual FSCK_ATTR((unused)), errcode_t error) |
| { |
| int i; |
| char *p; |
| ext2_filsys fs = (ext2_filsys) channel->app_data; |
| e2fsck_t ctx; |
| |
| ctx = (e2fsck_t) fs->priv_data; |
| |
| /* |
| * If more than one block was read, try reading each block |
| * separately. We could use the actual bytes read to figure |
| * out where to start, but we don't bother. |
| */ |
| if (count > 1) { |
| p = (char *) data; |
| for (i=0; i < count; i++, p += channel->block_size, block++) { |
| error = io_channel_read_blk(channel, block, |
| 1, p); |
| if (error) |
| return error; |
| } |
| return 0; |
| } |
| if (operation) |
| printf(_("Error reading block %lu (%s) while %s. "), block, |
| error_message(error), operation); |
| else |
| printf(_("Error reading block %lu (%s). "), block, |
| error_message(error)); |
| preenhalt(ctx); |
| if (ask(ctx, _("Ignore error"), 1)) { |
| if (ask(ctx, _("Force rewrite"), 1)) |
| io_channel_write_blk(channel, block, 1, data); |
| return 0; |
| } |
| |
| return error; |
| } |
| |
| static errcode_t |
| e2fsck_handle_write_error(io_channel channel, unsigned long block, int count, |
| const void *data, size_t size FSCK_ATTR((unused)), |
| int actual FSCK_ATTR((unused)), errcode_t error) |
| { |
| int i; |
| const char *p; |
| ext2_filsys fs = (ext2_filsys) channel->app_data; |
| e2fsck_t ctx; |
| |
| ctx = (e2fsck_t) fs->priv_data; |
| |
| /* |
| * If more than one block was written, try writing each block |
| * separately. We could use the actual bytes read to figure |
| * out where to start, but we don't bother. |
| */ |
| if (count > 1) { |
| p = (const char *) data; |
| for (i=0; i < count; i++, p += channel->block_size, block++) { |
| error = io_channel_write_blk(channel, block, |
| 1, p); |
| if (error) |
| return error; |
| } |
| return 0; |
| } |
| |
| if (operation) |
| printf(_("Error writing block %lu (%s) while %s. "), block, |
| error_message(error), operation); |
| else |
| printf(_("Error writing block %lu (%s). "), block, |
| error_message(error)); |
| preenhalt(ctx); |
| if (ask(ctx, _("Ignore error"), 1)) |
| return 0; |
| |
| return error; |
| } |
| |
| static const char *ehandler_operation(const char *op) |
| { |
| const char *ret = operation; |
| |
| operation = op; |
| return ret; |
| } |
| |
| static void ehandler_init(io_channel channel) |
| { |
| channel->read_error = e2fsck_handle_read_error; |
| channel->write_error = e2fsck_handle_write_error; |
| } |
| |
| /* |
| * journal.c --- code for handling the "ext3" journal |
| * |
| * Copyright (C) 2000 Andreas Dilger |
| * Copyright (C) 2000 Theodore Ts'o |
| * |
| * Parts of the code are based on fs/jfs/journal.c by Stephen C. Tweedie |
| * Copyright (C) 1999 Red Hat Software |
| * |
| * This file may be redistributed under the terms of the |
| * GNU General Public License version 2 or at your discretion |
| * any later version. |
| */ |
| |
| /* |
| * Define USE_INODE_IO to use the inode_io.c / fileio.c codepaths. |
| * This creates a larger static binary, and a smaller binary using |
| * shared libraries. It's also probably slightly less CPU-efficient, |
| * which is why it's not on by default. But, it's a good way of |
| * testing the functions in inode_io.c and fileio.c. |
| */ |
| #undef USE_INODE_IO |
| |
| /* Kernel compatibility functions for handling the journal. These allow us |
| * to use the recovery.c file virtually unchanged from the kernel, so we |
| * don't have to do much to keep kernel and user recovery in sync. |
| */ |
| static int journal_bmap(journal_t *journal, blk_t block, unsigned long *phys) |
| { |
| #ifdef USE_INODE_IO |
| *phys = block; |
| return 0; |
| #else |
| struct inode *inode = journal->j_inode; |
| errcode_t retval; |
| blk_t pblk; |
| |
| if (!inode) { |
| *phys = block; |
| return 0; |
| } |
| |
| retval= ext2fs_bmap(inode->i_ctx->fs, inode->i_ino, |
| &inode->i_ext2, NULL, 0, block, &pblk); |
| *phys = pblk; |
| return retval; |
| #endif |
| } |
| |
| static struct buffer_head *getblk(kdev_t kdev, blk_t blocknr, int blocksize) |
| { |
| struct buffer_head *bh; |
| |
| bh = e2fsck_allocate_memory(kdev->k_ctx, sizeof(*bh), "block buffer"); |
| if (!bh) |
| return NULL; |
| |
| bh->b_ctx = kdev->k_ctx; |
| if (kdev->k_dev == K_DEV_FS) |
| bh->b_io = kdev->k_ctx->fs->io; |
| else |
| bh->b_io = kdev->k_ctx->journal_io; |
| bh->b_size = blocksize; |
| bh->b_blocknr = blocknr; |
| |
| return bh; |
| } |
| |
| static void sync_blockdev(kdev_t kdev) |
| { |
| io_channel io; |
| |
| if (kdev->k_dev == K_DEV_FS) |
| io = kdev->k_ctx->fs->io; |
| else |
| io = kdev->k_ctx->journal_io; |
| |
| io_channel_flush(io); |
| } |
| |
| static void ll_rw_block(int rw, int nr, struct buffer_head *bhp[]) |
| { |
| int retval; |
| struct buffer_head *bh; |
| |
| for (; nr > 0; --nr) { |
| bh = *bhp++; |
| if (rw == READ && !bh->b_uptodate) { |
| retval = io_channel_read_blk(bh->b_io, |
| bh->b_blocknr, |
| 1, bh->b_data); |
| if (retval) { |
| bb_error_msg("while reading block %lu", |
| (unsigned long) bh->b_blocknr); |
| bh->b_err = retval; |
| continue; |
| } |
| bh->b_uptodate = 1; |
| } else if (rw == WRITE && bh->b_dirty) { |
| retval = io_channel_write_blk(bh->b_io, |
| bh->b_blocknr, |
| 1, bh->b_data); |
| if (retval) { |
| bb_error_msg("while writing block %lu", |
| (unsigned long) bh->b_blocknr); |
| bh->b_err = retval; |
| continue; |
| } |
| bh->b_dirty = 0; |
| bh->b_uptodate = 1; |
| } |
| } |
| } |
| |
| static void mark_buffer_dirty(struct buffer_head *bh) |
| { |
| bh->b_dirty = 1; |
| } |
| |
| static inline void mark_buffer_clean(struct buffer_head * bh) |
| { |
| bh->b_dirty = 0; |
| } |
| |
| static void brelse(struct buffer_head *bh) |
| { |
| if (bh->b_dirty) |
| ll_rw_block(WRITE, 1, &bh); |
| ext2fs_free_mem(&bh); |
| } |
| |
| static int buffer_uptodate(struct buffer_head *bh) |
| { |
| return bh->b_uptodate; |
| } |
| |
| static inline void mark_buffer_uptodate(struct buffer_head *bh, int val) |
| { |
| bh->b_uptodate = val; |
| } |
| |
| static void wait_on_buffer(struct buffer_head *bh) |
| { |
| if (!bh->b_uptodate) |
| ll_rw_block(READ, 1, &bh); |
| } |
| |
| |
| static void e2fsck_clear_recover(e2fsck_t ctx, int error) |
| { |
| ctx->fs->super->s_feature_incompat &= ~EXT3_FEATURE_INCOMPAT_RECOVER; |
| |
| /* if we had an error doing journal recovery, we need a full fsck */ |
| if (error) |
| ctx->fs->super->s_state &= ~EXT2_VALID_FS; |
| ext2fs_mark_super_dirty(ctx->fs); |
| } |
| |
| static errcode_t e2fsck_get_journal(e2fsck_t ctx, journal_t **ret_journal) |
| { |
| struct ext2_super_block *sb = ctx->fs->super; |
| struct ext2_super_block jsuper; |
| struct problem_context pctx; |
| struct buffer_head *bh; |
| struct inode *j_inode = NULL; |
| struct kdev_s *dev_fs = NULL, *dev_journal; |
| const char *journal_name = NULL; |
| journal_t *journal = NULL; |
| errcode_t retval = 0; |
| io_manager io_ptr = 0; |
| unsigned long start = 0; |
| blk_t blk; |
| int ext_journal = 0; |
| int tried_backup_jnl = 0; |
| int i; |
| |
| clear_problem_context(&pctx); |
| |
| journal = e2fsck_allocate_memory(ctx, sizeof(journal_t), "journal"); |
| if (!journal) { |
| return EXT2_ET_NO_MEMORY; |
| } |
| |
| dev_fs = e2fsck_allocate_memory(ctx, 2*sizeof(struct kdev_s), "kdev"); |
| if (!dev_fs) { |
| retval = EXT2_ET_NO_MEMORY; |
| goto errout; |
| } |
| dev_journal = dev_fs+1; |
| |
| dev_fs->k_ctx = dev_journal->k_ctx = ctx; |
| dev_fs->k_dev = K_DEV_FS; |
| dev_journal->k_dev = K_DEV_JOURNAL; |
| |
| journal->j_dev = dev_journal; |
| journal->j_fs_dev = dev_fs; |
| journal->j_inode = NULL; |
| journal->j_blocksize = ctx->fs->blocksize; |
| |
| if (uuid_is_null(sb->s_journal_uuid)) { |
| if (!sb->s_journal_inum) |
| return EXT2_ET_BAD_INODE_NUM; |
| j_inode = e2fsck_allocate_memory(ctx, sizeof(*j_inode), |
| "journal inode"); |
| if (!j_inode) { |
| retval = EXT2_ET_NO_MEMORY; |
| goto errout; |
| } |
| |
| j_inode->i_ctx = ctx; |
| j_inode->i_ino = sb->s_journal_inum; |
| |
| if ((retval = ext2fs_read_inode(ctx->fs, |
| sb->s_journal_inum, |
| &j_inode->i_ext2))) { |
| try_backup_journal: |
| if (sb->s_jnl_backup_type != EXT3_JNL_BACKUP_BLOCKS || |
| tried_backup_jnl) |
| goto errout; |
| memset(&j_inode->i_ext2, 0, sizeof(struct ext2_inode)); |
| memcpy(&j_inode->i_ext2.i_block[0], sb->s_jnl_blocks, |
| EXT2_N_BLOCKS*4); |
| j_inode->i_ext2.i_size = sb->s_jnl_blocks[16]; |
| j_inode->i_ext2.i_links_count = 1; |
| j_inode->i_ext2.i_mode = LINUX_S_IFREG | 0600; |
| tried_backup_jnl++; |
| } |
| if (!j_inode->i_ext2.i_links_count || |
| !LINUX_S_ISREG(j_inode->i_ext2.i_mode)) { |
| retval = EXT2_ET_NO_JOURNAL; |
| goto try_backup_journal; |
| } |
| if (j_inode->i_ext2.i_size / journal->j_blocksize < |
| JFS_MIN_JOURNAL_BLOCKS) { |
| retval = EXT2_ET_JOURNAL_TOO_SMALL; |
| goto try_backup_journal; |
| } |
| for (i=0; i < EXT2_N_BLOCKS; i++) { |
| blk = j_inode->i_ext2.i_block[i]; |
| if (!blk) { |
| if (i < EXT2_NDIR_BLOCKS) { |
| retval = EXT2_ET_JOURNAL_TOO_SMALL; |
| goto try_backup_journal; |
| } |
| continue; |
| } |
| if (blk < sb->s_first_data_block || |
| blk >= sb->s_blocks_count) { |
| retval = EXT2_ET_BAD_BLOCK_NUM; |
| goto try_backup_journal; |
| } |
| } |
| journal->j_maxlen = j_inode->i_ext2.i_size / journal->j_blocksize; |
| |
| #ifdef USE_INODE_IO |
| retval = ext2fs_inode_io_intern2(ctx->fs, sb->s_journal_inum, |
| &j_inode->i_ext2, |
| &journal_name); |
| if (retval) |
| goto errout; |
| |
| io_ptr = inode_io_manager; |
| #else |
| journal->j_inode = j_inode; |
| ctx->journal_io = ctx->fs->io; |
| if ((retval = journal_bmap(journal, 0, &start)) != 0) |
| goto errout; |
| #endif |
| } else { |
| ext_journal = 1; |
| if (!ctx->journal_name) { |
| char uuid[37]; |
| |
| uuid_unparse(sb->s_journal_uuid, uuid); |
| ctx->journal_name = blkid_get_devname(ctx->blkid, |
| "UUID", uuid); |
| if (!ctx->journal_name) |
| ctx->journal_name = blkid_devno_to_devname(sb->s_journal_dev); |
| } |
| journal_name = ctx->journal_name; |
| |
| if (!journal_name) { |
| fix_problem(ctx, PR_0_CANT_FIND_JOURNAL, &pctx); |
| return EXT2_ET_LOAD_EXT_JOURNAL; |
| } |
| |
| io_ptr = unix_io_manager; |
| } |
| |
| #ifndef USE_INODE_IO |
| if (ext_journal) |
| #endif |
| retval = io_ptr->open(journal_name, IO_FLAG_RW, |
| &ctx->journal_io); |
| if (retval) |
| goto errout; |
| |
| io_channel_set_blksize(ctx->journal_io, ctx->fs->blocksize); |
| |
| if (ext_journal) { |
| if (ctx->fs->blocksize == 1024) |
| start = 1; |
| bh = getblk(dev_journal, start, ctx->fs->blocksize); |
| if (!bh) { |
| retval = EXT2_ET_NO_MEMORY; |
| goto errout; |
| } |
| ll_rw_block(READ, 1, &bh); |
| if ((retval = bh->b_err) != 0) |
| goto errout; |
| memcpy(&jsuper, start ? bh->b_data : bh->b_data + 1024, |
| sizeof(jsuper)); |
| brelse(bh); |
| #if BB_BIG_ENDIAN |
| if (jsuper.s_magic == ext2fs_swab16(EXT2_SUPER_MAGIC)) |
| ext2fs_swap_super(&jsuper); |
| #endif |
| if (jsuper.s_magic != EXT2_SUPER_MAGIC || |
| !(jsuper.s_feature_incompat & EXT3_FEATURE_INCOMPAT_JOURNAL_DEV)) { |
| fix_problem(ctx, PR_0_EXT_JOURNAL_BAD_SUPER, &pctx); |
| retval = EXT2_ET_LOAD_EXT_JOURNAL; |
| goto errout; |
| } |
| /* Make sure the journal UUID is correct */ |
| if (memcmp(jsuper.s_uuid, ctx->fs->super->s_journal_uuid, |
| sizeof(jsuper.s_uuid))) { |
| fix_problem(ctx, PR_0_JOURNAL_BAD_UUID, &pctx); |
| retval = EXT2_ET_LOAD_EXT_JOURNAL; |
| goto errout; |
| } |
| |
| journal->j_maxlen = jsuper.s_blocks_count; |
| start++; |
| } |
| |
| if (!(bh = getblk(dev_journal, start, journal->j_blocksize))) { |
| retval = EXT2_ET_NO_MEMORY; |
| goto errout; |
| } |
| |
| journal->j_sb_buffer = bh; |
| journal->j_superblock = (journal_superblock_t *)bh->b_data; |
| |
| #ifdef USE_INODE_IO |
| ext2fs_free_mem(&j_inode); |
| #endif |
| |
| *ret_journal = journal; |
| return 0; |
| |
| errout: |
| ext2fs_free_mem(&dev_fs); |
| ext2fs_free_mem(&j_inode); |
| ext2fs_free_mem(&journal); |
| return retval; |
| } |
| |
| static errcode_t e2fsck_journal_fix_bad_inode(e2fsck_t ctx, |
| struct problem_context *pctx) |
| { |
| struct ext2_super_block *sb = ctx->fs->super; |
| int recover = ctx->fs->super->s_feature_incompat & |
| EXT3_FEATURE_INCOMPAT_RECOVER; |
| int has_journal = ctx->fs->super->s_feature_compat & |
| EXT3_FEATURE_COMPAT_HAS_JOURNAL; |
| |
| if (has_journal || sb->s_journal_inum) { |
| /* The journal inode is bogus, remove and force full fsck */ |
| pctx->ino = sb->s_journal_inum; |
| if (fix_problem(ctx, PR_0_JOURNAL_BAD_INODE, pctx)) { |
| if (has_journal && sb->s_journal_inum) |
| printf("*** ext3 journal has been deleted - " |
| "filesystem is now ext2 only ***\n\n"); |
| sb->s_feature_compat &= ~EXT3_FEATURE_COMPAT_HAS_JOURNAL; |
| sb->s_journal_inum = 0; |
| ctx->flags |= E2F_FLAG_JOURNAL_INODE; /* FIXME: todo */ |
| e2fsck_clear_recover(ctx, 1); |
| return 0; |
| } |
| return EXT2_ET_BAD_INODE_NUM; |
| } else if (recover) { |
| if (fix_problem(ctx, PR_0_JOURNAL_RECOVER_SET, pctx)) { |
| e2fsck_clear_recover(ctx, 1); |
| return 0; |
| } |
| return EXT2_ET_UNSUPP_FEATURE; |
| } |
| return 0; |
| } |
| |
| #define V1_SB_SIZE 0x0024 |
| static void clear_v2_journal_fields(journal_t *journal) |
| { |
| e2fsck_t ctx = journal->j_dev->k_ctx; |
| struct problem_context pctx; |
| |
| clear_problem_context(&pctx); |
| |
| if (!fix_problem(ctx, PR_0_CLEAR_V2_JOURNAL, &pctx)) |
| return; |
| |
| memset(((char *) journal->j_superblock) + V1_SB_SIZE, 0, |
| ctx->fs->blocksize-V1_SB_SIZE); |
| mark_buffer_dirty(journal->j_sb_buffer); |
| } |
| |
| |
| static errcode_t e2fsck_journal_load(journal_t *journal) |
| { |
| e2fsck_t ctx = journal->j_dev->k_ctx; |
| journal_superblock_t *jsb; |
| struct buffer_head *jbh = journal->j_sb_buffer; |
| struct problem_context pctx; |
| |
| clear_problem_context(&pctx); |
| |
| ll_rw_block(READ, 1, &jbh); |
| if (jbh->b_err) { |
| bb_error_msg(_("reading journal superblock")); |
| return jbh->b_err; |
| } |
| |
| jsb = journal->j_superblock; |
| /* If we don't even have JFS_MAGIC, we probably have a wrong inode */ |
| if (jsb->s_header.h_magic != htonl(JFS_MAGIC_NUMBER)) |
| return e2fsck_journal_fix_bad_inode(ctx, &pctx); |
| |
| switch (ntohl(jsb->s_header.h_blocktype)) { |
| case JFS_SUPERBLOCK_V1: |
| journal->j_format_version = 1; |
| if (jsb->s_feature_compat || |
| jsb->s_feature_incompat || |
| jsb->s_feature_ro_compat || |
| jsb->s_nr_users) |
| clear_v2_journal_fields(journal); |
| break; |
| |
| case JFS_SUPERBLOCK_V2: |
| journal->j_format_version = 2; |
| if (ntohl(jsb->s_nr_users) > 1 && |
| uuid_is_null(ctx->fs->super->s_journal_uuid)) |
| clear_v2_journal_fields(journal); |
| if (ntohl(jsb->s_nr_users) > 1) { |
| fix_problem(ctx, PR_0_JOURNAL_UNSUPP_MULTIFS, &pctx); |
| return EXT2_ET_JOURNAL_UNSUPP_VERSION; |
| } |
| break; |
| |
| /* |
| * These should never appear in a journal super block, so if |
| * they do, the journal is badly corrupted. |
| */ |
| case JFS_DESCRIPTOR_BLOCK: |
| case JFS_COMMIT_BLOCK: |
| case JFS_REVOKE_BLOCK: |
| return EXT2_ET_CORRUPT_SUPERBLOCK; |
| |
| /* If we don't understand the superblock major type, but there |
| * is a magic number, then it is likely to be a new format we |
| * just don't understand, so leave it alone. */ |
| default: |
| return EXT2_ET_JOURNAL_UNSUPP_VERSION; |
| } |
| |
| if (JFS_HAS_INCOMPAT_FEATURE(journal, ~JFS_KNOWN_INCOMPAT_FEATURES)) |
| return EXT2_ET_UNSUPP_FEATURE; |
| |
| if (JFS_HAS_RO_COMPAT_FEATURE(journal, ~JFS_KNOWN_ROCOMPAT_FEATURES)) |
| return EXT2_ET_RO_UNSUPP_FEATURE; |
| |
| /* We have now checked whether we know enough about the journal |
| * format to be able to proceed safely, so any other checks that |
| * fail we should attempt to recover from. */ |
| if (jsb->s_blocksize != htonl(journal->j_blocksize)) { |
| bb_error_msg(_("%s: no valid journal superblock found"), |
| ctx->device_name); |
| return EXT2_ET_CORRUPT_SUPERBLOCK; |
| } |
| |
| if (ntohl(jsb->s_maxlen) < journal->j_maxlen) |
| journal->j_maxlen = ntohl(jsb->s_maxlen); |
| else if (ntohl(jsb->s_maxlen) > journal->j_maxlen) { |
| bb_error_msg(_("%s: journal too short"), |
| ctx->device_name); |
| return EXT2_ET_CORRUPT_SUPERBLOCK; |
| } |
| |
| journal->j_tail_sequence = ntohl(jsb->s_sequence); |
| journal->j_transaction_sequence = journal->j_tail_sequence; |
| journal->j_tail = ntohl(jsb->s_start); |
| journal->j_first = ntohl(jsb->s_first); |
| journal->j_last = ntohl(jsb->s_maxlen); |
| |
| return 0; |
| } |
| |
| static void e2fsck_journal_reset_super(e2fsck_t ctx, journal_superblock_t *jsb, |
| journal_t *journal) |
| { |
| char *p; |
| union { |
| uuid_t uuid; |
| __u32 val[4]; |
| } u; |
| __u32 new_seq = 0; |
| int i; |
| |
| /* Leave a valid existing V1 superblock signature alone. |
| * Anything unrecognizable we overwrite with a new V2 |
| * signature. */ |
| |
| if (jsb->s_header.h_magic != htonl(JFS_MAGIC_NUMBER) || |
| jsb->s_header.h_blocktype != htonl(JFS_SUPERBLOCK_V1)) { |
| jsb->s_header.h_magic = htonl(JFS_MAGIC_NUMBER); |
| jsb->s_header.h_blocktype = htonl(JFS_SUPERBLOCK_V2); |
| } |
| |
| /* Zero out everything else beyond the superblock header */ |
| |
| p = ((char *) jsb) + sizeof(journal_header_t); |
| memset (p, 0, ctx->fs->blocksize-sizeof(journal_header_t)); |
| |
| jsb->s_blocksize = htonl(ctx->fs->blocksize); |
| jsb->s_maxlen = htonl(journal->j_maxlen); |
| jsb->s_first = htonl(1); |
| |
| /* Initialize the journal sequence number so that there is "no" |
| * chance we will find old "valid" transactions in the journal. |
| * This avoids the need to zero the whole journal (slow to do, |
| * and risky when we are just recovering the filesystem). |
| */ |
| uuid_generate(u.uuid); |
| for (i = 0; i < 4; i ++) |
| new_seq ^= u.val[i]; |
| jsb->s_sequence = htonl(new_seq); |
| |
| mark_buffer_dirty(journal->j_sb_buffer); |
| ll_rw_block(WRITE, 1, &journal->j_sb_buffer); |
| } |
| |
| static errcode_t e2fsck_journal_fix_corrupt_super(e2fsck_t ctx, |
| journal_t *journal, |
| struct problem_context *pctx) |
| { |
| struct ext2_super_block *sb = ctx->fs->super; |
| int recover = ctx->fs->super->s_feature_incompat & |
| EXT3_FEATURE_INCOMPAT_RECOVER; |
| |
| if (sb->s_feature_compat & EXT3_FEATURE_COMPAT_HAS_JOURNAL) { |
| if (fix_problem(ctx, PR_0_JOURNAL_BAD_SUPER, pctx)) { |
| e2fsck_journal_reset_super(ctx, journal->j_superblock, |
| journal); |
| journal->j_transaction_sequence = 1; |
| e2fsck_clear_recover(ctx, recover); |
| return 0; |
| } |
| return EXT2_ET_CORRUPT_SUPERBLOCK; |
| } else if (e2fsck_journal_fix_bad_inode(ctx, pctx)) |
| return EXT2_ET_CORRUPT_SUPERBLOCK; |
| |
| return 0; |
| } |
| |
| static void e2fsck_journal_release(e2fsck_t ctx, journal_t *journal, |
| int reset, int drop) |
| { |
| journal_superblock_t *jsb; |
| |
| if (drop) |
| mark_buffer_clean(journal->j_sb_buffer); |
| else if (!(ctx->options & E2F_OPT_READONLY)) { |
| jsb = journal->j_superblock; |
| jsb->s_sequence = htonl(journal->j_transaction_sequence); |
| if (reset) |
| jsb->s_start = 0; /* this marks the journal as empty */ |
| mark_buffer_dirty(journal->j_sb_buffer); |
| } |
| brelse(journal->j_sb_buffer); |
| |
| if (ctx->journal_io) { |
| if (ctx->fs && ctx->fs->io != ctx->journal_io) |
| io_channel_close(ctx->journal_io); |
| ctx->journal_io = 0; |
| } |
| |
| #ifndef USE_INODE_IO |
| ext2fs_free_mem(&journal->j_inode); |
| #endif |
| ext2fs_free_mem(&journal->j_fs_dev); |
| ext2fs_free_mem(&journal); |
| } |
| |
| /* |
| * This function makes sure that the superblock fields regarding the |
| * journal are consistent. |
| */ |
| static int e2fsck_check_ext3_journal(e2fsck_t ctx) |
| { |
| struct ext2_super_block *sb = ctx->fs->super; |
| journal_t *journal; |
| int recover = ctx->fs->super->s_feature_incompat & |
| EXT3_FEATURE_INCOMPAT_RECOVER; |
| struct problem_context pctx; |
| problem_t problem; |
| int reset = 0, force_fsck = 0; |
| int retval; |
| |
| /* If we don't have any journal features, don't do anything more */ |
| if (!(sb->s_feature_compat & EXT3_FEATURE_COMPAT_HAS_JOURNAL) && |
| !recover && sb->s_journal_inum == 0 && sb->s_journal_dev == 0 && |
| uuid_is_null(sb->s_journal_uuid)) |
| return 0; |
| |
| clear_problem_context(&pctx); |
| pctx.num = sb->s_journal_inum; |
| |
| retval = e2fsck_get_journal(ctx, &journal); |
| if (retval) { |
| if ((retval == EXT2_ET_BAD_INODE_NUM) || |
| (retval == EXT2_ET_BAD_BLOCK_NUM) || |
| (retval == EXT2_ET_JOURNAL_TOO_SMALL) || |
| (retval == EXT2_ET_NO_JOURNAL)) |
| return e2fsck_journal_fix_bad_inode(ctx, &pctx); |
| return retval; |
| } |
| |
| retval = e2fsck_journal_load(journal); |
| if (retval) { |
| if ((retval == EXT2_ET_CORRUPT_SUPERBLOCK) || |
| ((retval == EXT2_ET_UNSUPP_FEATURE) && |
| (!fix_problem(ctx, PR_0_JOURNAL_UNSUPP_INCOMPAT, |
| &pctx))) || |
| ((retval == EXT2_ET_RO_UNSUPP_FEATURE) && |
| (!fix_problem(ctx, PR_0_JOURNAL_UNSUPP_ROCOMPAT, |
| &pctx))) || |
| ((retval == EXT2_ET_JOURNAL_UNSUPP_VERSION) && |
| (!fix_problem(ctx, PR_0_JOURNAL_UNSUPP_VERSION, &pctx)))) |
| retval = e2fsck_journal_fix_corrupt_super(ctx, journal, |
| &pctx); |
| e2fsck_journal_release(ctx, journal, 0, 1); |
| return retval; |
| } |
| |
| /* |
| * We want to make the flags consistent here. We will not leave with |
| * needs_recovery set but has_journal clear. We can't get in a loop |
| * with -y, -n, or -p, only if a user isn't making up their mind. |
| */ |
| no_has_journal: |
| if (!(sb->s_feature_compat & EXT3_FEATURE_COMPAT_HAS_JOURNAL)) { |
| recover = sb->s_feature_incompat & EXT3_FEATURE_INCOMPAT_RECOVER; |
| pctx.str = "inode"; |
| if (fix_problem(ctx, PR_0_JOURNAL_HAS_JOURNAL, &pctx)) { |
| if (recover && |
| !fix_problem(ctx, PR_0_JOURNAL_RECOVER_SET, &pctx)) |
| goto no_has_journal; |
| /* |
| * Need a full fsck if we are releasing a |
| * journal stored on a reserved inode. |
| */ |
| force_fsck = recover || |
| (sb->s_journal_inum < EXT2_FIRST_INODE(sb)); |
| /* Clear all of the journal fields */ |
| sb->s_journal_inum = 0; |
| sb->s_journal_dev = 0; |
| memset(sb->s_journal_uuid, 0, |
| sizeof(sb->s_journal_uuid)); |
| e2fsck_clear_recover(ctx, force_fsck); |
| } else if (!(ctx->options & E2F_OPT_READONLY)) { |
| sb->s_feature_compat |= EXT3_FEATURE_COMPAT_HAS_JOURNAL; |
| ext2fs_mark_super_dirty(ctx->fs); |
| } |
| } |
| |
| if (sb->s_feature_compat & EXT3_FEATURE_COMPAT_HAS_JOURNAL && |
| !(sb->s_feature_incompat & EXT3_FEATURE_INCOMPAT_RECOVER) && |
| journal->j_superblock->s_start != 0) { |
| /* Print status information */ |
| fix_problem(ctx, PR_0_JOURNAL_RECOVERY_CLEAR, &pctx); |
| if (ctx->superblock) |
| problem = PR_0_JOURNAL_RUN_DEFAULT; |
| else |
| problem = PR_0_JOURNAL_RUN; |
| if (fix_problem(ctx, problem, &pctx)) { |
| ctx->options |= E2F_OPT_FORCE; |
| sb->s_feature_incompat |= |
| EXT3_FEATURE_INCOMPAT_RECOVER; |
| ext2fs_mark_super_dirty(ctx->fs); |
| } else if (fix_problem(ctx, |
| PR_0_JOURNAL_RESET_JOURNAL, &pctx)) { |
| reset = 1; |
| sb->s_state &= ~EXT2_VALID_FS; |
| ext2fs_mark_super_dirty(ctx->fs); |
| } |
| /* |
| * If the user answers no to the above question, we |
| * ignore the fact that journal apparently has data; |
| * accidentally replaying over valid data would be far |
| * worse than skipping a questionable recovery. |
| * |
| * XXX should we abort with a fatal error here? What |
| * will the ext3 kernel code do if a filesystem with |
| * !NEEDS_RECOVERY but with a non-zero |
| * journal->j_superblock->s_start is mounted? |
| */ |
| } |
| |
| e2fsck_journal_release(ctx, journal, reset, 0); |
| return retval; |
| } |
| |
| static errcode_t recover_ext3_journal(e2fsck_t ctx) |
| { |
| journal_t *journal; |
| int retval; |
| |
| journal_init_revoke_caches(); |
| retval = e2fsck_get_journal(ctx, &journal); |
| if (retval) |
| return retval; |
| |
| retval = e2fsck_journal_load(journal); |
| if (retval) |
| goto errout; |
| |
| retval = journal_init_revoke(journal, 1024); |
| if (retval) |
| goto errout; |
| |
| retval = -journal_recover(journal); |
| if (retval) |
| goto errout; |
| |
| if (journal->j_superblock->s_errno) { |
| ctx->fs->super->s_state |= EXT2_ERROR_FS; |
| ext2fs_mark_super_dirty(ctx->fs); |
| journal->j_superblock->s_errno = 0; |
| mark_buffer_dirty(journal->j_sb_buffer); |
| } |
| |
| errout: |
| journal_destroy_revoke(journal); |
| journal_destroy_revoke_caches(); |
| e2fsck_journal_release(ctx, journal, 1, 0); |
| return retval; |
| } |
| |
| static int e2fsck_run_ext3_journal(e2fsck_t ctx) |
| { |
| io_manager io_ptr = ctx->fs->io->manager; |
| int blocksize = ctx->fs->blocksize; |
| errcode_t retval, recover_retval; |
| |
| printf(_("%s: recovering journal\n"), ctx->device_name); |
| if (ctx->options & E2F_OPT_READONLY) { |
| printf(_("%s: won't do journal recovery while read-only\n"), |
| ctx->device_name); |
| return EXT2_ET_FILE_RO; |
| } |
| |
| if (ctx->fs->flags & EXT2_FLAG_DIRTY) |
| ext2fs_flush(ctx->fs); /* Force out any modifications */ |
| |
| recover_retval = recover_ext3_journal(ctx); |
| |
| /* |
| * Reload the filesystem context to get up-to-date data from disk |
| * because journal recovery will change the filesystem under us. |
| */ |
| ext2fs_close(ctx->fs); |
| retval = ext2fs_open(ctx->filesystem_name, EXT2_FLAG_RW, |
| ctx->superblock, blocksize, io_ptr, |
| &ctx->fs); |
| |
| if (retval) { |
| bb_error_msg(_("while trying to re-open %s"), |
| ctx->device_name); |
| bb_error_msg_and_die(0); |
| } |
| ctx->fs->priv_data = ctx; |
| |
| /* Set the superblock flags */ |
| e2fsck_clear_recover(ctx, recover_retval); |
| return recover_retval; |
| } |
| |
| /* |
| * This function will move the journal inode from a visible file in |
| * the filesystem directory hierarchy to the reserved inode if necessary. |
| */ |
| static const char *const journal_names[] = { |
| ".journal", "journal", ".journal.dat", "journal.dat", 0 }; |
| |
| static void e2fsck_move_ext3_journal(e2fsck_t ctx) |
| { |
| struct ext2_super_block *sb = ctx->fs->super; |
| struct problem_context pctx; |
| struct ext2_inode inode; |
| ext2_filsys fs = ctx->fs; |
| ext2_ino_t ino; |
| errcode_t retval; |
| const char *const * cpp; |
| int group, mount_flags; |
| |
| clear_problem_context(&pctx); |
| |
| /* |
| * If the filesystem is opened read-only, or there is no |
| * journal, then do nothing. |
| */ |
| if ((ctx->options & E2F_OPT_READONLY) || |
| (sb->s_journal_inum == 0) || |
| !(sb->s_feature_compat & EXT3_FEATURE_COMPAT_HAS_JOURNAL)) |
| return; |
| |
| /* |
| * Read in the journal inode |
| */ |
| if (ext2fs_read_inode(fs, sb->s_journal_inum, &inode) != 0) |
| return; |
| |
| /* |
| * If it's necessary to backup the journal inode, do so. |
| */ |
| if ((sb->s_jnl_backup_type == 0) || |
| ((sb->s_jnl_backup_type == EXT3_JNL_BACKUP_BLOCKS) && |
| memcmp(inode.i_block, sb->s_jnl_blocks, EXT2_N_BLOCKS*4))) { |
| if (fix_problem(ctx, PR_0_BACKUP_JNL, &pctx)) { |
| memcpy(sb->s_jnl_blocks, inode.i_block, |
| EXT2_N_BLOCKS*4); |
| sb->s_jnl_blocks[16] = inode.i_size; |
| sb->s_jnl_backup_type = EXT3_JNL_BACKUP_BLOCKS; |
| ext2fs_mark_super_dirty(fs); |
| fs->flags &= ~EXT2_FLAG_MASTER_SB_ONLY; |
| } |
| } |
| |
| /* |
| * If the journal is already the hidden inode, then do nothing |
| */ |
| if (sb->s_journal_inum == EXT2_JOURNAL_INO) |
| return; |
| |
| /* |
| * The journal inode had better have only one link and not be readable. |
| */ |
| if (inode.i_links_count != 1) |
| return; |
| |
| /* |
| * If the filesystem is mounted, or we can't tell whether |
| * or not it's mounted, do nothing. |
| */ |
| retval = ext2fs_check_if_mounted(ctx->filesystem_name, &mount_flags); |
| if (retval || (mount_flags & EXT2_MF_MOUNTED)) |
| return; |
| |
| /* |
| * If we can't find the name of the journal inode, then do |
| * nothing. |
| */ |
| for (cpp = journal_names; *cpp; cpp++) { |
| retval = ext2fs_lookup(fs, EXT2_ROOT_INO, *cpp, |
| strlen(*cpp), 0, &ino); |
| if ((retval == 0) && (ino == sb->s_journal_inum)) |
| break; |
| } |
| if (*cpp == 0) |
| return; |
| |
| /* We need the inode bitmap to be loaded */ |
| retval = ext2fs_read_bitmaps(fs); |
| if (retval) |
| return; |
| |
| pctx.str = *cpp; |
| if (!fix_problem(ctx, PR_0_MOVE_JOURNAL, &pctx)) |
| return; |
| |
| /* |
| * OK, we've done all the checks, let's actually move the |
| * journal inode. Errors at this point mean we need to force |
| * an ext2 filesystem check. |
| */ |
| if ((retval = ext2fs_unlink(fs, EXT2_ROOT_INO, *cpp, ino, 0)) != 0) |
| goto err_out; |
| if ((retval = ext2fs_write_inode(fs, EXT2_JOURNAL_INO, &inode)) != 0) |
| goto err_out; |
| sb->s_journal_inum = EXT2_JOURNAL_INO; |
| ext2fs_mark_super_dirty(fs); |
| fs->flags &= ~EXT2_FLAG_MASTER_SB_ONLY; |
| inode.i_links_count = 0; |
| inode.i_dtime = time(NULL); |
| if ((retval = ext2fs_write_inode(fs, ino, &inode)) != 0) |
| goto err_out; |
| |
| group = ext2fs_group_of_ino(fs, ino); |
| ext2fs_unmark_inode_bitmap(fs->inode_map, ino); |
| ext2fs_mark_ib_dirty(fs); |
| fs->group_desc[group].bg_free_inodes_count++; |
| fs->super->s_free_inodes_count++; |
| return; |
| |
| err_out: |
| pctx.errcode = retval; |
| fix_problem(ctx, PR_0_ERR_MOVE_JOURNAL, &pctx); |
| fs->super->s_state &= ~EXT2_VALID_FS; |
| ext2fs_mark_super_dirty(fs); |
| } |
| |
| /* |
| * message.c --- print e2fsck messages (with compression) |
| * |
| * print_e2fsck_message() prints a message to the user, using |
| * compression techniques and expansions of abbreviations. |
| * |
| * The following % expansions are supported: |
| * |
| * %b <blk> block number |
| * %B <blkcount> integer |
| * %c <blk2> block number |
| * %Di <dirent>->ino inode number |
| * %Dn <dirent>->name string |
| * %Dr <dirent>->rec_len |
| * %Dl <dirent>->name_len |
| * %Dt <dirent>->filetype |
| * %d <dir> inode number |
| * %g <group> integer |
| * %i <ino> inode number |
| * %Is <inode> -> i_size |
| * %IS <inode> -> i_extra_isize |
| * %Ib <inode> -> i_blocks |
| * %Il <inode> -> i_links_count |
| * %Im <inode> -> i_mode |
| * %IM <inode> -> i_mtime |
| * %IF <inode> -> i_faddr |
| * %If <inode> -> i_file_acl |
| * %Id <inode> -> i_dir_acl |
| * %Iu <inode> -> i_uid |
| * %Ig <inode> -> i_gid |
| * %j <ino2> inode number |
| * %m <com_err error message> |
| * %N <num> |
| * %p ext2fs_get_pathname of directory <ino> |
| * %P ext2fs_get_pathname of <dirent>->ino with <ino2> as |
| * the containing directory. (If dirent is NULL |
| * then return the pathname of directory <ino2>) |
| * %q ext2fs_get_pathname of directory <dir> |
| * %Q ext2fs_get_pathname of directory <ino> with <dir> as |
| * the containing directory. |
| * %s <str> miscellaneous string |
| * %S backup superblock |
| * %X <num> hexadecimal format |
| * |
| * The following '@' expansions are supported: |
| * |
| * @a extended attribute |
| * @A error allocating |
| * @b block |
| * @B bitmap |
| * @c compress |
| * @C conflicts with some other fs block |
| * @D deleted |
| * @d directory |
| * @e entry |
| * @E Entry '%Dn' in %p (%i) |
| * @f filesystem |
| * @F for @i %i (%Q) is |
| * @g group |
| * @h HTREE directory inode |
| * @i inode |
| * @I illegal |
| * @j journal |
| * @l lost+found |
| * @L is a link |
| * @m multiply-claimed |
| * @n invalid |
| * @o orphaned |
| * @p problem in |
| * @r root inode |
| * @s should be |
| * @S superblock |
| * @u unattached |
| * @v device |
| * @z zero-length |
| */ |
| |
| |
| /* |
| * This structure defines the abbreviations used by the text strings |
| * below. The first character in the string is the index letter. An |
| * abbreviation of the form '@<i>' is expanded by looking up the index |
| * letter <i> in the table below. |
| */ |
| static const char *const abbrevs[] = { |
| N_("aextended attribute"), |
| N_("Aerror allocating"), |
| N_("bblock"), |
| N_("Bbitmap"), |
| N_("ccompress"), |
| N_("Cconflicts with some other fs @b"), |
| N_("iinode"), |
| N_("Iillegal"), |
| N_("jjournal"), |
| N_("Ddeleted"), |
| N_("ddirectory"), |
| N_("eentry"), |
| N_("E@e '%Dn' in %p (%i)"), |
| N_("ffilesystem"), |
| N_("Ffor @i %i (%Q) is"), |
| N_("ggroup"), |
| N_("hHTREE @d @i"), |
| N_("llost+found"), |
| N_("Lis a link"), |
| N_("mmultiply-claimed"), |
| N_("ninvalid"), |
| N_("oorphaned"), |
| N_("pproblem in"), |
| N_("rroot @i"), |
| N_("sshould be"), |
| N_("Ssuper@b"), |
| N_("uunattached"), |
| N_("vdevice"), |
| N_("zzero-length"), |
| "@@", |
| 0 |
| }; |
| |
| /* |
| * Give more user friendly names to the "special" inodes. |
| */ |
| #define num_special_inodes 11 |
| static const char *const special_inode_name[] = |
| { |
| N_("<The NULL inode>"), /* 0 */ |
| N_("<The bad blocks inode>"), /* 1 */ |
| "/", /* 2 */ |
| N_("<The ACL index inode>"), /* 3 */ |
| N_("<The ACL data inode>"), /* 4 */ |
| N_("<The boot loader inode>"), /* 5 */ |
| N_("<The undelete directory inode>"), /* 6 */ |
| N_("<The group descriptor inode>"), /* 7 */ |
| N_("<The journal inode>"), /* 8 */ |
| N_("<Reserved inode 9>"), /* 9 */ |
| N_("<Reserved inode 10>"), /* 10 */ |
| }; |
| |
| /* |
| * This function does "safe" printing. It will convert non-printable |
| * ASCII characters using '^' and M- notation. |
| */ |
| static void safe_print(const char *cp, int len) |
| { |
| unsigned char ch; |
| |
| if (len < 0) |
| len = strlen(cp); |
| |
| while (len--) { |
| ch = *cp++; |
| if (ch > 128) { |
| fputs("M-", stdout); |
| ch -= 128; |
| } |
| if ((ch < 32) || (ch == 0x7f)) { |
| bb_putchar('^'); |
| ch ^= 0x40; /* ^@, ^A, ^B; ^? for DEL */ |
| } |
| bb_putchar(ch); |
| } |
| } |
| |
| |
| /* |
| * This function prints a pathname, using the ext2fs_get_pathname |
| * function |
| */ |
| static void print_pathname(ext2_filsys fs, ext2_ino_t dir, ext2_ino_t ino) |
| { |
| errcode_t retval; |
| char *path; |
| |
| if (!dir && (ino < num_special_inodes)) { |
| fputs(_(special_inode_name[ino]), stdout); |
| return; |
| } |
| |
| retval = ext2fs_get_pathname(fs, dir, ino, &path); |
| if (retval) |
| fputs("???", stdout); |
| else { |
| safe_print(path, -1); |
| ext2fs_free_mem(&path); |
| } |
| } |
| |
| static void print_e2fsck_message(e2fsck_t ctx, const char *msg, |
| struct problem_context *pctx, int first); |
| /* |
| * This function handles the '@' expansion. We allow recursive |
| * expansion; an @ expression can contain further '@' and '%' |
| * expressions. |
| */ |
| static void expand_at_expression(e2fsck_t ctx, char ch, |
| struct problem_context *pctx, |
| int *first) |
| { |
| const char *const *cpp; |
| const char *str; |
| |
| /* Search for the abbreviation */ |
| for (cpp = abbrevs; *cpp; cpp++) { |
| if (ch == *cpp[0]) |
| break; |
| } |
| if (*cpp) { |
| str = _(*cpp) + 1; |
| if (*first && islower(*str)) { |
| *first = 0; |
| bb_putchar(toupper(*str++)); |
| } |
| print_e2fsck_message(ctx, str, pctx, *first); |
| } else |
| printf("@%c", ch); |
| } |
| |
| /* |
| * This function expands '%IX' expressions |
| */ |
| static void expand_inode_expression(char ch, |
| struct problem_context *ctx) |
| { |
| struct ext2_inode *inode; |
| struct ext2_inode_large *large_inode; |
| char * time_str; |
| time_t t; |
| int do_gmt = -1; |
| |
| if (!ctx || !ctx->inode) |
| goto no_inode; |
| |
| inode = ctx->inode; |
| large_inode = (struct ext2_inode_large *) inode; |
| |
| switch (ch) { |
| case 's': |
| if (LINUX_S_ISDIR(inode->i_mode)) |
| printf("%u", inode->i_size); |
| else { |
| printf("%"PRIu64, (inode->i_size | |
| ((uint64_t) inode->i_size_high << 32))); |
| } |
| break; |
| case 'S': |
| printf("%u", large_inode->i_extra_isize); |
| break; |
| case 'b': |
| printf("%u", inode->i_blocks); |
| break; |
| case 'l': |
| printf("%d", inode->i_links_count); |
| break; |
| case 'm': |
| printf("0%o", inode->i_mode); |
| break; |
| case 'M': |
| /* The diet libc doesn't respect the TZ environemnt variable */ |
| if (do_gmt == -1) { |
| time_str = getenv("TZ"); |
| if (!time_str) |
| time_str = ""; |
| do_gmt = !strcmp(time_str, "GMT"); |
| } |
| t = inode->i_mtime; |
| time_str = asctime(do_gmt ? gmtime(&t) : localtime(&t)); |
| printf("%.24s", time_str); |
| break; |
| case 'F': |
| printf("%u", inode->i_faddr); |
| break; |
| case 'f': |
| printf("%u", inode->i_file_acl); |
| break; |
| case 'd': |
| printf("%u", (LINUX_S_ISDIR(inode->i_mode) ? |
| inode->i_dir_acl : 0)); |
| break; |
| case 'u': |
| printf("%d", (inode->i_uid | |
| (inode->osd2.linux2.l_i_uid_high << 16))); |
| break; |
| case 'g': |
| printf("%d", (inode->i_gid | |
| (inode->osd2.linux2.l_i_gid_high << 16))); |
| break; |
| default: |
| no_inode: |
| printf("%%I%c", ch); |
| break; |
| } |
| } |
| |
| /* |
| * This function expands '%dX' expressions |
| */ |
| static void expand_dirent_expression(char ch, |
| struct problem_context *ctx) |
| { |
| struct ext2_dir_entry *dirent; |
| int len; |
| |
| if (!ctx || !ctx->dirent) |
| goto no_dirent; |
| |
| dirent = ctx->dirent; |
| |
| switch (ch) { |
| case 'i': |
| printf("%u", dirent->inode); |
| break; |
| case 'n': |
| len = dirent->name_len & 0xFF; |
| if (len > EXT2_NAME_LEN) |
| len = EXT2_NAME_LEN; |
| if (len > dirent->rec_len) |
| len = dirent->rec_len; |
| safe_print(dirent->name, len); |
| break; |
| case 'r': |
| printf("%u", dirent->rec_len); |
| break; |
| case 'l': |
| printf("%u", dirent->name_len & 0xFF); |
| break; |
| case 't': |
| printf("%u", dirent->name_len >> 8); |
| break; |
| default: |
| no_dirent: |
| printf("%%D%c", ch); |
| break; |
| } |
| } |
| |
| static void expand_percent_expression(ext2_filsys fs, char ch, |
| struct problem_context *ctx) |
| { |
| if (!ctx) |
| goto no_context; |
| |
| switch (ch) { |
| case '%': |
| bb_putchar('%'); |
| break; |
| case 'b': |
| printf("%u", ctx->blk); |
| break; |
| case 'B': |
| printf("%"PRIi64, ctx->blkcount); |
| break; |
| case 'c': |
| printf("%u", ctx->blk2); |
| break; |
| case 'd': |
| printf("%u", ctx->dir); |
| break; |
| case 'g': |
| printf("%d", ctx->group); |
| break; |
| case 'i': |
| printf("%u", ctx->ino); |
| break; |
| case 'j': |
| printf("%u", ctx->ino2); |
| break; |
| case 'm': |
| fputs(error_message(ctx->errcode), stdout); |
| break; |
| case 'N': |
| printf("%"PRIi64, ctx->num); |
| break; |
| case 'p': |
| print_pathname(fs, ctx->ino, 0); |
| break; |
| case 'P': |
| print_pathname(fs, ctx->ino2, |
| ctx->dirent ? ctx->dirent->inode : 0); |
| break; |
| case 'q': |
| print_pathname(fs, ctx->dir, 0); |
| break; |
| case 'Q': |
| print_pathname(fs, ctx->dir, ctx->ino); |
| break; |
| case 'S': |
| printf("%d", get_backup_sb(NULL, fs, NULL, NULL)); |
| break; |
| case 's': |
| fputs((ctx->str ? ctx->str : "NULL"), stdout); |
| break; |
| case 'X': |
| printf("0x%"PRIi64, ctx->num); |
| break; |
| default: |
| no_context: |
| printf("%%%c", ch); |
| break; |
| } |
| } |
| |
| |
| static void print_e2fsck_message(e2fsck_t ctx, const char *msg, |
| struct problem_context *pctx, int first) |
| { |
| ext2_filsys fs = ctx->fs; |
| const char * cp; |
| int i; |
| |
| e2fsck_clear_progbar(ctx); |
| for (cp = msg; *cp; cp++) { |
| if (cp[0] == '@') { |
| cp++; |
| expand_at_expression(ctx, *cp, pctx, &first); |
| } else if (cp[0] == '%' && cp[1] == 'I') { |
| cp += 2; |
| expand_inode_expression(*cp, pctx); |
| } else if (cp[0] == '%' && cp[1] == 'D') { |
| cp += 2; |
| expand_dirent_expression(*cp, pctx); |
| } else if ((cp[0] == '%')) { |
| cp++; |
| expand_percent_expression(fs, *cp, pctx); |
| } else { |
| for (i=0; cp[i]; i++) |
| if ((cp[i] == '@') || cp[i] == '%') |
| break; |
| printf("%.*s", i, cp); |
| cp += i-1; |
| } |
| first = 0; |
| } |
| } |
| |
| |
| /* |
| * region.c --- code which manages allocations within a region. |
| */ |
| |
| struct region_el { |
| region_addr_t start; |
| region_addr_t end; |
| struct region_el *next; |
| }; |
| |
| struct region_struct { |
| region_addr_t min; |
| region_addr_t max; |
| struct region_el *allocated; |
| }; |
| |
| static region_t region_create(region_addr_t min, region_addr_t max) |
| { |
| region_t region; |
| |
| region = xzalloc(sizeof(struct region_struct)); |
| region->min = min; |
| region->max = max; |
| return region; |
| } |
| |
| static void region_free(region_t region) |
| { |
| struct region_el *r, *next; |
| |
| for (r = region->allocated; r; r = next) { |
| next = r->next; |
| free(r); |
| } |
| memset(region, 0, sizeof(struct region_struct)); |
| free(region); |
| } |
| |
| static int region_allocate(region_t region, region_addr_t start, int n) |
| { |
| struct region_el *r, *new_region, *prev, *next; |
| region_addr_t end; |
| |
| end = start+n; |
| if ((start < region->min) || (end > region->max)) |
| return -1; |
| if (n == 0) |
| return 1; |
| |
| /* |
| * Search through the linked list. If we find that it |
| * conflicts witih something that's already allocated, return |
| * 1; if we can find an existing region which we can grow, do |
| * so. Otherwise, stop when we find the appropriate place |
| * insert a new region element into the linked list. |
| */ |
| for (r = region->allocated, prev=NULL; r; prev = r, r = r->next) { |
| if (((start >= r->start) && (start < r->end)) || |
| ((end > r->start) && (end <= r->end)) || |
| ((start <= r->start) && (end >= r->end))) |
| return 1; |
| if (end == r->start) { |
| r->start = start; |
| return 0; |
| } |
| if (start == r->end) { |
| if ((next = r->next)) { |
| if (end > next->start) |
| return 1; |
| if (end == next->start) { |
| r->end = next->end; |
| r->next = next->next; |
| free(next); |
| return 0; |
| } |
| } |
| r->end = end; |
| return 0; |
| } |
| if (start < r->start) |
| break; |
| } |
| /* |
| * Insert a new region element structure into the linked list |
| */ |
| new_region = xmalloc(sizeof(struct region_el)); |
| new_region->start = start; |
| new_region->end = start + n; |
| new_region->next = r; |
| if (prev) |
| prev->next = new_region; |
| else |
| region->allocated = new_region; |
| return 0; |
| } |
| |
| /* |
| * pass1.c -- pass #1 of e2fsck: sequential scan of the inode table |
| * |
| * Pass 1 of e2fsck iterates over all the inodes in the filesystems, |
| * and applies the following tests to each inode: |
| * |
| * - The mode field of the inode must be legal. |
| * - The size and block count fields of the inode are correct. |
| * - A data block must not be used by another inode |
| * |
| * Pass 1 also gathers the collects the following information: |
| * |
| * - A bitmap of which inodes are in use. (inode_used_map) |
| * - A bitmap of which inodes are directories. (inode_dir_map) |
| * - A bitmap of which inodes are regular files. (inode_reg_map) |
| * - A bitmap of which inodes have bad fields. (inode_bad_map) |
| * - A bitmap of which inodes are imagic inodes. (inode_imagic_map) |
| * - A bitmap of which blocks are in use. (block_found_map) |
| * - A bitmap of which blocks are in use by two inodes (block_dup_map) |
| * - The data blocks of the directory inodes. (dir_map) |
| * |
| * Pass 1 is designed to stash away enough information so that the |
| * other passes should not need to read in the inode information |
| * during the normal course of a filesystem check. (Althogh if an |
| * inconsistency is detected, other passes may need to read in an |
| * inode to fix it.) |
| * |
| * Note that pass 1B will be invoked if there are any duplicate blocks |
| * found. |
| */ |
| |
| |
| static int process_block(ext2_filsys fs, blk_t *blocknr, |
| e2_blkcnt_t blockcnt, blk_t ref_blk, |
| int ref_offset, void *priv_data); |
| static int process_bad_block(ext2_filsys fs, blk_t *block_nr, |
| e2_blkcnt_t blockcnt, blk_t ref_blk, |
| int ref_offset, void *priv_data); |
| static void check_blocks(e2fsck_t ctx, struct problem_context *pctx, |
| char *block_buf); |
| static void mark_table_blocks(e2fsck_t ctx); |
| static void alloc_imagic_map(e2fsck_t ctx); |
| static void mark_inode_bad(e2fsck_t ctx, ino_t ino); |
| static void handle_fs_bad_blocks(e2fsck_t ctx); |
| static void process_inodes(e2fsck_t ctx, char *block_buf); |
| static int process_inode_cmp(const void *a, const void *b); |
| static errcode_t scan_callback(ext2_filsys fs, |
| dgrp_t group, void * priv_data); |
| static void adjust_extattr_refcount(e2fsck_t ctx, ext2_refcount_t refcount, |
| char *block_buf, int adjust_sign); |
| /* static char *describe_illegal_block(ext2_filsys fs, blk_t block); */ |
| |
| static void e2fsck_write_inode_full(e2fsck_t ctx, unsigned long ino, |
| struct ext2_inode * inode, int bufsize, |
| const char *proc); |
| |
| struct process_block_struct_1 { |
| ext2_ino_t ino; |
| unsigned is_dir:1, is_reg:1, clear:1, suppress:1, |
| fragmented:1, compressed:1, bbcheck:1; |
| blk_t num_blocks; |
| blk_t max_blocks; |
| e2_blkcnt_t last_block; |
| int num_illegal_blocks; |
| blk_t previous_block; |
| struct ext2_inode *inode; |
| struct problem_context *pctx; |
| ext2fs_block_bitmap fs_meta_blocks; |
| e2fsck_t ctx; |
| }; |
| |
| struct process_inode_block { |
| ext2_ino_t ino; |
| struct ext2_inode inode; |
| }; |
| |
| struct scan_callback_struct { |
| e2fsck_t ctx; |
| char *block_buf; |
| }; |
| |
| /* |
| * For the inodes to process list. |
| */ |
| static struct process_inode_block *inodes_to_process; |
| static int process_inode_count; |
| |
| static __u64 ext2_max_sizes[EXT2_MAX_BLOCK_LOG_SIZE - |
| EXT2_MIN_BLOCK_LOG_SIZE + 1]; |
| |
| /* |
| * Free all memory allocated by pass1 in preparation for restarting |
| * things. |
| */ |
| static void unwind_pass1(void) |
| { |
| ext2fs_free_mem(&inodes_to_process); |
| } |
| |
| /* |
| * Check to make sure a device inode is real. Returns 1 if the device |
| * checks out, 0 if not. |
| * |
| * Note: this routine is now also used to check FIFO's and Sockets, |
| * since they have the same requirement; the i_block fields should be |
| * zero. |
| */ |
| static int |
| e2fsck_pass1_check_device_inode(ext2_filsys fs, struct ext2_inode *inode) |
| { |
| int i; |
| |
| /* |
| * If i_blocks is non-zero, or the index flag is set, then |
| * this is a bogus device/fifo/socket |
| */ |
| if ((ext2fs_inode_data_blocks(fs, inode) != 0) || |
| (inode->i_flags & EXT2_INDEX_FL)) |
| return 0; |
| |
| /* |
| * We should be able to do the test below all the time, but |
| * because the kernel doesn't forcibly clear the device |
| * inode's additional i_block fields, there are some rare |
| * occasions when a legitimate device inode will have non-zero |
| * additional i_block fields. So for now, we only complain |
| * when the immutable flag is set, which should never happen |
| * for devices. (And that's when the problem is caused, since |
| * you can't set or clear immutable flags for devices.) Once |
| * the kernel has been fixed we can change this... |
| */ |
| if (inode->i_flags & (EXT2_IMMUTABLE_FL | EXT2_APPEND_FL)) { |
| for (i=4; i < EXT2_N_BLOCKS; i++) |
| if (inode->i_block[i]) |
| return 0; |
| } |
| return 1; |
| } |
| |
| /* |
| * Check to make sure a symlink inode is real. Returns 1 if the symlink |
| * checks out, 0 if not. |
| */ |
| static int |
| e2fsck_pass1_check_symlink(ext2_filsys fs, struct ext2_inode *inode, char *buf) |
| { |
| unsigned int len; |
| int i; |
| blk_t blocks; |
| |
| if ((inode->i_size_high || inode->i_size == 0) || |
| (inode->i_flags & EXT2_INDEX_FL)) |
| return 0; |
| |
| blocks = ext2fs_inode_data_blocks(fs, inode); |
| if (blocks) { |
| if ((inode->i_size >= fs->blocksize) || |
| (blocks != fs->blocksize >> 9) || |
| (inode->i_block[0] < fs->super->s_first_data_block) || |
| (inode->i_block[0] >= fs->super->s_blocks_count)) |
| return 0; |
| |
| for (i = 1; i < EXT2_N_BLOCKS; i++) |
| if (inode->i_block[i]) |
| return 0; |
| |
| if (io_channel_read_blk(fs->io, inode->i_block[0], 1, buf)) |
| return 0; |
| |
| len = strnlen(buf, fs->blocksize); |
| if (len == fs->blocksize) |
| return 0; |
| } else { |
| if (inode->i_size >= sizeof(inode->i_block)) |
| return 0; |
| |
| len = strnlen((char *)inode->i_block, sizeof(inode->i_block)); |
| if (len == sizeof(inode->i_block)) |
| return 0; |
| } |
| if (len != inode->i_size) |
| return 0; |
| return 1; |
| } |
| |
| /* |
| * If the immutable (or append-only) flag is set on the inode, offer |
| * to clear it. |
| */ |
| #define BAD_SPECIAL_FLAGS (EXT2_IMMUTABLE_FL | EXT2_APPEND_FL) |
| static void check_immutable(e2fsck_t ctx, struct problem_context *pctx) |
| { |
| if (!(pctx->inode->i_flags & BAD_SPECIAL_FLAGS)) |
| return; |
| |
| if (!fix_problem(ctx, PR_1_SET_IMMUTABLE, pctx)) |
| return; |
| |
| pctx->inode->i_flags &= ~BAD_SPECIAL_FLAGS; |
| e2fsck_write_inode(ctx, pctx->ino, pctx->inode, "pass1"); |
| } |
| |
| /* |
| * If device, fifo or socket, check size is zero -- if not offer to |
| * clear it |
| */ |
| static void check_size(e2fsck_t ctx, struct problem_context *pctx) |
| { |
| struct ext2_inode *inode = pctx->inode; |
| |
| if ((inode->i_size == 0) && (inode->i_size_high == 0)) |
| return; |
| |
| if (!fix_problem(ctx, PR_1_SET_NONZSIZE, pctx)) |
| return; |
| |
| inode->i_size = 0; |
| inode->i_size_high = 0; |
| e2fsck_write_inode(ctx, pctx->ino, pctx->inode, "pass1"); |
| } |
| |
| static void check_ea_in_inode(e2fsck_t ctx, struct problem_context *pctx) |
| { |
| struct ext2_super_block *sb = ctx->fs->super; |
| struct ext2_inode_large *inode; |
| struct ext2_ext_attr_entry *entry; |
| char *start, *end; |
| int storage_size, remain, offs; |
| int problem = 0; |
| |
| inode = (struct ext2_inode_large *) pctx->inode; |
| storage_size = EXT2_INODE_SIZE(ctx->fs->super) - EXT2_GOOD_OLD_INODE_SIZE - |
| inode->i_extra_isize; |
| start = ((char *) inode) + EXT2_GOOD_OLD_INODE_SIZE + |
| inode->i_extra_isize + sizeof(__u32); |
| end = (char *) inode + EXT2_INODE_SIZE(ctx->fs->super); |
| entry = (struct ext2_ext_attr_entry *) start; |
| |
| /* scan all entry's headers first */ |
| |
| /* take finish entry 0UL into account */ |
| remain = storage_size - sizeof(__u32); |
| offs = end - start; |
| |
| while (!EXT2_EXT_IS_LAST_ENTRY(entry)) { |
| |
| /* header eats this space */ |
| remain -= sizeof(struct ext2_ext_attr_entry); |
| |
| /* is attribute name valid? */ |
| if (EXT2_EXT_ATTR_SIZE(entry->e_name_len) > remain) { |
| pctx->num = entry->e_name_len; |
| problem = PR_1_ATTR_NAME_LEN; |
| goto fix; |
| } |
| |
| /* attribute len eats this space */ |
| remain -= EXT2_EXT_ATTR_SIZE(entry->e_name_len); |
| |
| /* check value size */ |
| if (entry->e_value_size == 0 || entry->e_value_size > remain) { |
| pctx->num = entry->e_value_size; |
| problem = PR_1_ATTR_VALUE_SIZE; |
| goto fix; |
| } |
| |
| /* check value placement */ |
| if (entry->e_value_offs + |
| EXT2_XATTR_SIZE(entry->e_value_size) != offs) { |
| printf("(entry->e_value_offs + entry->e_value_size: %d, offs: %d)\n", entry->e_value_offs + entry->e_value_size, offs); |
| pctx->num = entry->e_value_offs; |
| problem = PR_1_ATTR_VALUE_OFFSET; |
| goto fix; |
| } |
| |
| /* e_value_block must be 0 in inode's ea */ |
| if (entry->e_value_block != 0) { |
| pctx->num = entry->e_value_block; |
| problem = PR_1_ATTR_VALUE_BLOCK; |
| goto fix; |
| } |
| |