Theodore Ts'o | 21c84b7 | 1997-04-29 16:15:03 +0000 | [diff] [blame] | 1 | /* |
| 2 | * problem.c --- report filesystem problems to the user |
| 3 | * |
| 4 | * Copyright 1996, 1997 by Theodore Ts'o |
| 5 | * |
| 6 | * %Begin-Header% |
| 7 | * This file may be redistributed under the terms of the GNU Public |
| 8 | * License. |
| 9 | * %End-Header% |
| 10 | */ |
| 11 | |
| 12 | #include <stdlib.h> |
| 13 | #include <unistd.h> |
| 14 | #include <string.h> |
| 15 | #include <ctype.h> |
| 16 | #include <termios.h> |
| 17 | |
| 18 | #include "e2fsck.h" |
| 19 | |
| 20 | #include "problem.h" |
| 21 | |
| 22 | #define PROMPT_FIX 0 |
| 23 | #define PROMPT_CLEAR 1 |
| 24 | #define PROMPT_RELOCATE 2 |
| 25 | #define PROMPT_ALLOCATE 3 |
| 26 | #define PROMPT_EXPAND 4 |
| 27 | #define PROMPT_CONNECT 5 |
| 28 | #define PROMPT_CREATE 6 |
| 29 | #define PROMPT_SALVAGE 7 |
| 30 | #define PROMPT_TRUNCATE 8 |
| 31 | #define PROMPT_CLEAR_INODE 9 |
| 32 | |
| 33 | /* |
| 34 | * These are the prompts which are used to ask the user if they want |
| 35 | * to fix a problem. |
| 36 | */ |
| 37 | static const char *prompt[] = { |
| 38 | "Fix", /* 0 */ |
| 39 | "Clear", /* 1 */ |
| 40 | "Relocate", /* 2 */ |
| 41 | "Allocate", /* 3 */ |
| 42 | "Expand", /* 4 */ |
| 43 | "Connect to /lost+found", /* 5 */ |
| 44 | "Create", /* 6 */ |
| 45 | "Salvage", /* 7 */ |
| 46 | "Truncate", /* 8 */ |
| 47 | "Clear inode" /* 9 */ |
| 48 | }; |
| 49 | |
| 50 | /* |
| 51 | * These messages are printed when we are preen mode and we will be |
| 52 | * automatically fixing the problem. |
| 53 | */ |
| 54 | static const char *preen_msg[] = { |
| 55 | "FIXED", /* 0 */ |
| 56 | "CLEARED", /* 1 */ |
| 57 | "RELOCATED", /* 2 */ |
| 58 | "ALLOCATED", /* 3 */ |
| 59 | "EXPANDED", /* 4 */ |
| 60 | "RECONNECTED", /* 5 */ |
| 61 | "CREATED", /* 6 */ |
| 62 | "SALVAGED", /* 7 */ |
| 63 | "TRUNCATED", /* 8 */ |
| 64 | "INODE CLEARED" /* 9 */ |
| 65 | }; |
| 66 | |
| 67 | static struct e2fsck_problem problem_table[] = { |
| 68 | |
| 69 | /* Pre-Pass 1 errors */ |
| 70 | |
| 71 | /* Block bitmap not in group */ |
| 72 | { PR_0_BB_NOT_GROUP, "@b @B for @g %g is not in @g. (@b %b)\n", |
| 73 | PROMPT_RELOCATE, 0 }, |
| 74 | |
| 75 | /* Inode bitmap not in group */ |
| 76 | { PR_0_IB_NOT_GROUP, "@i @B for @g %g is not in @g. (@b %b)\n", |
| 77 | PROMPT_RELOCATE, 0 }, |
| 78 | |
| 79 | /* Inode table not in group */ |
| 80 | { PR_0_ITABLE_NOT_GROUP, |
| 81 | "@i table for @g %g is not in @g. (@b %b)\n" |
| 82 | "WARNING: SEVERE DATA LOSS POSSIBLE.\n", |
| 83 | PROMPT_RELOCATE, 0 }, |
| 84 | |
| 85 | /* Pass 1 errors */ |
| 86 | |
| 87 | /* Root directory is not an inode */ |
| 88 | { PR_1_ROOT_NO_DIR, "@r is not a @d. ", |
| 89 | PROMPT_CLEAR, 0 }, |
| 90 | |
| 91 | /* Root directory has dtime set */ |
| 92 | { PR_1_ROOT_DTIME, |
| 93 | "@r has dtime set (probably due to old mke2fs). ", |
| 94 | PROMPT_FIX, PR_PREEN_OK }, |
| 95 | |
| 96 | /* Reserved inode has bad mode */ |
| 97 | { PR_1_RESERVED_BAD_MODE, |
| 98 | "Reserved @i %i has bad mode. ", |
| 99 | PROMPT_CLEAR, PR_PREEN_OK }, |
| 100 | |
| 101 | /* Deleted inode has zero dtime */ |
| 102 | { PR_1_ZERO_DTIME, |
| 103 | "@D @i %i has zero dtime. ", |
| 104 | PROMPT_FIX, PR_PREEN_OK }, |
| 105 | |
| 106 | /* Inode in use, but dtime set */ |
| 107 | { PR_1_SET_DTIME, |
| 108 | "@i %i is in use, but has dtime set. ", |
| 109 | PROMPT_FIX, PR_PREEN_OK }, |
| 110 | |
| 111 | /* Zero-length directory */ |
| 112 | { PR_1_ZERO_LENGTH_DIR, |
| 113 | "@i %i is a @z @d. ", |
| 114 | PROMPT_CLEAR, PR_PREEN_OK }, |
| 115 | |
| 116 | /* Block bitmap conflicts with some other fs block */ |
| 117 | { PR_1_BB_CONFLICT, |
Theodore Ts'o | da2e97f | 1997-06-12 04:28:07 +0000 | [diff] [blame] | 118 | "@g %g's @b @B at %b @C.\n", |
Theodore Ts'o | 21c84b7 | 1997-04-29 16:15:03 +0000 | [diff] [blame] | 119 | PROMPT_RELOCATE, 0 }, |
| 120 | |
| 121 | /* Inode bitmap conflicts with some other fs block */ |
| 122 | { PR_1_IB_CONFLICT, |
Theodore Ts'o | da2e97f | 1997-06-12 04:28:07 +0000 | [diff] [blame] | 123 | "@g %g's @i @B at %b @C.\n", |
Theodore Ts'o | 21c84b7 | 1997-04-29 16:15:03 +0000 | [diff] [blame] | 124 | PROMPT_RELOCATE, 0 }, |
| 125 | |
| 126 | /* Inode table conflicts with some other fs block */ |
| 127 | { PR_1_ITABLE_CONFLICT, |
| 128 | "@g %g's @i table at %b @C.\n", |
| 129 | PROMPT_RELOCATE, 0 }, |
| 130 | |
| 131 | /* Block bitmap is on a bad block */ |
| 132 | { PR_1_BB_BAD_BLOCK, |
| 133 | "@g %g's @b @B (%b) is bad. ", |
| 134 | PROMPT_RELOCATE, 0 }, |
| 135 | |
| 136 | /* Inode bitmap is on a bad block */ |
| 137 | { PR_1_IB_BAD_BLOCK, |
| 138 | "@g %g's @i @B (%b) is bad. ", |
| 139 | PROMPT_RELOCATE, 0 }, |
| 140 | |
| 141 | /* Inode has incorrect i_size */ |
| 142 | { PR_1_BAD_I_SIZE, |
| 143 | "@i %i, i_size is %Is, @s %N. ", |
| 144 | PROMPT_FIX, PR_PREEN_OK }, |
| 145 | |
| 146 | /* Inode has incorrect i_blocks */ |
| 147 | { PR_1_BAD_I_BLOCKS, |
| 148 | "@i %i, i_blocks is %Ib, @s %N. ", |
| 149 | PROMPT_FIX, PR_PREEN_OK }, |
| 150 | |
| 151 | /* Illegal block number in inode */ |
| 152 | { PR_1_ILLEGAL_BLOCK_NUM, |
| 153 | "Illegal @b #%B (%b) in @i %i. ", |
| 154 | PROMPT_CLEAR, PR_LATCH_BLOCK }, |
| 155 | |
| 156 | /* Block number overlaps fs metadata */ |
| 157 | { PR_1_BLOCK_OVERLAPS_METADATA, |
| 158 | "@b #%B (%b) overlaps filesystem metadata in @i %i. ", |
| 159 | PROMPT_CLEAR, PR_LATCH_BLOCK }, |
| 160 | |
| 161 | /* Inode has illegal blocks (latch question) */ |
| 162 | { PR_1_INODE_BLOCK_LATCH, |
| 163 | "@i %i has illegal @b(s). ", |
| 164 | PROMPT_CLEAR, 0 }, |
| 165 | |
| 166 | /* Too many bad blocks in inode */ |
| 167 | { PR_1_TOO_MANY_BAD_BLOCKS, |
| 168 | "Too many illegal @bs in @i %i.\n", |
| 169 | PROMPT_CLEAR_INODE, PR_NO_OK }, |
| 170 | |
| 171 | /* Illegal block number in bad block inode */ |
| 172 | { PR_1_BB_ILLEGAL_BLOCK_NUM, |
| 173 | "Illegal @b #%B (%b) in bad @b @i. ", |
| 174 | PROMPT_CLEAR, PR_LATCH_BBLOCK }, |
| 175 | |
| 176 | /* Bad block inode has illegal blocks (latch question) */ |
| 177 | { PR_1_INODE_BBLOCK_LATCH, |
| 178 | "Bad @b @i has illegal @b(s). ", |
| 179 | PROMPT_CLEAR, 0 }, |
| 180 | |
| 181 | /* Pass 1b errors */ |
| 182 | |
| 183 | /* File has duplicate blocks */ |
| 184 | { PR_1B_DUP_FILE, |
| 185 | "File %Q (@i #%i, mod time %IM) \n" |
| 186 | " has %B duplicate @b(s), shared with %N file(s):\n", |
| 187 | PROMPT_FIX, PR_MSG_ONLY }, |
| 188 | |
| 189 | /* List of files sharing duplicate blocks */ |
| 190 | { PR_1B_DUP_FILE_LIST, |
| 191 | "\t%Q (@i #%i, mod time %IM)\n", |
| 192 | PROMPT_FIX, PR_MSG_ONLY }, |
| 193 | |
Theodore Ts'o | 521e368 | 1997-04-29 17:48:10 +0000 | [diff] [blame] | 194 | /* File sharing blocks with filesystem metadata */ |
| 195 | { PR_1B_SHARE_METADATA, |
| 196 | "\t<filesystem metadata>\n", |
| 197 | PROMPT_FIX, PR_MSG_ONLY }, |
Theodore Ts'o | 21c84b7 | 1997-04-29 16:15:03 +0000 | [diff] [blame] | 198 | |
| 199 | /* Pass 2 errors */ |
| 200 | |
| 201 | /* Bad inode number for '.' */ |
| 202 | { PR_2_BAD_INODE_DOT, |
| 203 | "Bad @i number for '.' in @d @i %i.\n", |
| 204 | PROMPT_FIX, 0 }, |
| 205 | |
| 206 | /* Directory entry has bad inode number */ |
| 207 | { PR_2_BAD_INO, |
| 208 | "@E has bad @i #: %Di.\n", |
| 209 | PROMPT_CLEAR, 0 }, |
| 210 | |
| 211 | /* Directory entry has deleted or unused inode */ |
| 212 | { PR_2_UNUSED_INODE, |
| 213 | "@E has @D/unused @i %Di. ", |
| 214 | PROMPT_CLEAR, PR_PREEN_OK }, |
| 215 | |
| 216 | /* Directry entry is link to '.' */ |
| 217 | { PR_2_LINK_DOT, |
| 218 | "@E @L to '.' ", |
| 219 | PROMPT_CLEAR, 0 }, |
| 220 | |
| 221 | /* Directory entry points to inode now located in a bad block */ |
| 222 | { PR_2_BB_INODE, |
| 223 | "@E points to @i (%Di) located in a bad @b.\n", |
| 224 | PROMPT_CLEAR, 0 }, |
| 225 | |
| 226 | /* Directory entry contains a link to a directory */ |
| 227 | { PR_2_LINK_DIR, |
| 228 | "@E @L to @d %P (%Di).\n", |
| 229 | PROMPT_CLEAR, 0 }, |
| 230 | |
| 231 | /* Directory entry contains a link to the root directry */ |
| 232 | { PR_2_LINK_ROOT, |
| 233 | "@E @L to the @r.\n", |
| 234 | PROMPT_CLEAR, 0 }, |
| 235 | |
| 236 | /* Directory entry has illegal characters in its name */ |
| 237 | { PR_2_BAD_NAME, |
| 238 | "@E has illegal characters in its name.\n", |
| 239 | PROMPT_FIX, 0 }, |
| 240 | |
| 241 | /* Missing '.' in directory inode */ |
| 242 | { PR_2_MISSING_DOT, |
| 243 | "Missing '.' in @d @i %i.\n", |
| 244 | PROMPT_FIX, 0 }, |
| 245 | |
| 246 | /* Missing '..' in directory inode */ |
| 247 | { PR_2_MISSING_DOT_DOT, |
| 248 | "Missing '..' in @d @i %i.\n", |
| 249 | PROMPT_FIX, 0 }, |
| 250 | |
| 251 | /* First entry in directory inode doesn't contain '.' */ |
| 252 | { PR_2_1ST_NOT_DOT, |
| 253 | "First @e '%Dn' (inode=%Di) in @d @i %i (%p) @s '.'\n", |
| 254 | PROMPT_FIX, 0 }, |
| 255 | |
| 256 | /* Second entry in directory inode doesn't contain '..' */ |
| 257 | { PR_2_2ND_NOT_DOT_DOT, |
| 258 | "Second @e '%Dn' (inode=%Di) in @d @i %i @s '..'\n", |
| 259 | PROMPT_FIX, 0 }, |
| 260 | |
| 261 | /* i_faddr should be zero */ |
| 262 | { PR_2_FADDR_ZERO, |
| 263 | "i_faddr @F %IF, @s zero.\n", |
| 264 | PROMPT_CLEAR, 0 }, |
| 265 | |
| 266 | /* i_file_acl should be zero */ |
| 267 | { PR_2_FILE_ACL_ZERO, |
| 268 | "i_file_acl @F %If, @s zero.\n", |
| 269 | PROMPT_CLEAR, 0 }, |
| 270 | |
| 271 | /* i_dir_acl should be zero */ |
| 272 | { PR_2_DIR_ACL_ZERO, |
| 273 | "i_dir_acl @F %Id, @s zero.\n", |
| 274 | PROMPT_CLEAR, 0 }, |
| 275 | |
| 276 | /* i_frag should be zero */ |
| 277 | { PR_2_FRAG_ZERO, |
| 278 | "i_frag @F %N, @s zero.\n", |
| 279 | PROMPT_CLEAR, 0 }, |
| 280 | |
| 281 | /* i_fsize should be zero */ |
| 282 | { PR_2_FSIZE_ZERO, |
| 283 | "i_fsize @F %N, @s zero.\n", |
| 284 | PROMPT_CLEAR, 0 }, |
| 285 | |
| 286 | /* inode has bad mode */ |
| 287 | { PR_2_BAD_MODE, |
| 288 | "@i %i (%Q) has a bad mode (%Im).\n", |
| 289 | PROMPT_CLEAR, 0 }, |
| 290 | |
| 291 | /* directory corrupted */ |
| 292 | { PR_2_DIR_CORRUPTED, |
| 293 | "@d @i %i, @b %B, offset %N: @d corrupted\n", |
| 294 | PROMPT_SALVAGE, 0 }, |
| 295 | |
| 296 | /* filename too long */ |
| 297 | { PR_2_FILENAME_LONG, |
| 298 | "@d @i %i, @b %B, offset %N: filename too long\n", |
| 299 | PROMPT_TRUNCATE, 0 }, |
| 300 | |
| 301 | /* Directory inode has a missing block (hole) */ |
| 302 | { PR_2_DIRECTORY_HOLE, |
| 303 | "@d @i %i has an unallocated @b #%B. ", |
| 304 | PROMPT_ALLOCATE, 0 }, |
| 305 | |
| 306 | /* '.' is not NULL terminated */ |
| 307 | { PR_2_DOT_NULL_TERM, |
| 308 | "'.' directory entry in @d @i %i is not NULL terminated\n", |
| 309 | PROMPT_FIX, 0 }, |
| 310 | |
| 311 | /* '..' is not NULL terminated */ |
| 312 | { PR_2_DOT_DOT_NULL_TERM, |
| 313 | "'..' directory entry in @d @i %i is not NULL terminated\n", |
| 314 | PROMPT_FIX, 0 }, |
| 315 | |
| 316 | /* Pass 3 errors */ |
| 317 | |
| 318 | /* Root inode not allocated */ |
| 319 | { PR_3_NO_ROOT_INODE, |
| 320 | "@r not allocated. ", |
| 321 | PROMPT_ALLOCATE, 0 }, |
| 322 | |
| 323 | /* No room in lost+found */ |
| 324 | { PR_3_EXPAND_LF_DIR, |
| 325 | "No room in @l @d. ", |
| 326 | PROMPT_EXPAND, 0 }, |
| 327 | |
| 328 | /* Unconnected directory inode */ |
| 329 | { PR_3_UNCONNECTED_DIR, |
| 330 | "Unconnected @d @i %i (%p)\n", |
| 331 | PROMPT_CONNECT, 0 }, |
| 332 | |
| 333 | /* /lost+found not found */ |
| 334 | { PR_3_NO_LF_DIR, |
| 335 | "/@l not found. ", |
| 336 | PROMPT_CREATE, 0 }, |
| 337 | |
| 338 | /* .. entry is incorrect */ |
| 339 | { PR_3_BAD_DOT_DOT, |
| 340 | "'..' in %Q (%i) is %P (%j), @s %q (%d).\n", |
| 341 | PROMPT_FIX, 0 }, |
| 342 | |
| 343 | /* Pass 4 errors */ |
| 344 | |
| 345 | /* Unattached zero-length inode */ |
| 346 | { PR_4_ZERO_LEN_INODE, |
| 347 | "@u @z @i %i. ", |
| 348 | PROMPT_CLEAR, PR_PREEN_OK|PR_NO_OK }, |
| 349 | |
| 350 | /* Unattached inode */ |
| 351 | { PR_4_UNATTACHED_INODE, |
| 352 | "@u @i %i\n", |
| 353 | PROMPT_CONNECT, 0 }, |
| 354 | |
| 355 | /* Inode ref count wrong */ |
| 356 | { PR_4_BAD_REF_COUNT, |
| 357 | "@i %i ref count is %Il, @s %N. ", |
| 358 | PROMPT_FIX, PR_PREEN_OK }, |
| 359 | |
| 360 | { 0 } |
| 361 | }; |
| 362 | |
| 363 | /* |
| 364 | * This is the latch flags register. It allows several problems to be |
| 365 | * "latched" together. This means that the user has to answer but one |
| 366 | * question for the set of problems, and all of the associated |
| 367 | * problems will be either fixed or not fixed. |
| 368 | */ |
| 369 | char pr_latch[7]; /* Latch flags register */ |
| 370 | char pr_suppress[7]; /* Latch groups which are suppressed */ |
| 371 | int latch_question[7] = { |
| 372 | PR_1_INODE_BLOCK_LATCH, |
| 373 | PR_1_INODE_BBLOCK_LATCH |
| 374 | }; |
| 375 | |
| 376 | static struct e2fsck_problem *find_problem(int code) |
| 377 | { |
| 378 | int i; |
| 379 | |
| 380 | for (i=0; problem_table[i].e2p_code; i++) { |
| 381 | if (problem_table[i].e2p_code == code) |
| 382 | return &problem_table[i]; |
| 383 | } |
| 384 | return 0; |
| 385 | } |
| 386 | |
| 387 | void reset_problem_latch(int mask) |
| 388 | { |
| 389 | pr_latch[PR_LATCH(mask)] = 0; |
| 390 | pr_suppress[PR_LATCH(mask)] = 0; |
| 391 | } |
| 392 | |
| 393 | void suppress_latch_group(int mask, int value) |
| 394 | { |
| 395 | pr_suppress[PR_LATCH(mask)] = value; |
| 396 | } |
| 397 | |
| 398 | void clear_problem_context(struct problem_context *ctx) |
| 399 | { |
| 400 | memset(ctx, 0, sizeof(struct problem_context)); |
| 401 | ctx->blkcount = -1; |
| 402 | ctx->group = -1; |
| 403 | } |
| 404 | |
| 405 | int fix_problem(ext2_filsys fs, int code, struct problem_context *ctx) |
| 406 | { |
| 407 | struct e2fsck_problem *ptr; |
| 408 | int def_yn, answer; |
| 409 | int latch; |
| 410 | int print_answer = 0; |
| 411 | int suppress = 0; |
| 412 | |
| 413 | ptr = find_problem(code); |
| 414 | if (!ptr) { |
| 415 | printf("Unhandled error code (%d)!\n", code); |
| 416 | return 0; |
| 417 | } |
| 418 | def_yn = (ptr->flags & PR_NO_DEFAULT) ? 0 : 1; |
| 419 | |
| 420 | /* |
| 421 | * Do special latch processing. This is where we ask the |
| 422 | * latch question, if it exists |
| 423 | */ |
| 424 | if (ptr->flags & PR_LATCH_MASK) { |
| 425 | latch = PR_LATCH(ptr->flags); |
| 426 | if (latch_question[latch] && !pr_latch[latch]) |
| 427 | pr_latch[latch] = fix_problem(fs, |
| 428 | latch_question[latch], |
| 429 | ctx) + 1; |
| 430 | if (pr_suppress[latch]) |
| 431 | suppress++; |
| 432 | } |
| 433 | |
| 434 | if (!suppress) { |
| 435 | if (preen) |
| 436 | printf("%s: ", device_name); |
| 437 | print_e2fsck_message(fs, ptr->e2p_description, ctx, 1); |
| 438 | } |
| 439 | if (!(ptr->flags & PR_PREEN_OK)) |
| 440 | preenhalt(fs); |
| 441 | |
| 442 | if (ptr->flags & PR_MSG_ONLY) |
| 443 | return 1; |
| 444 | |
| 445 | if (preen) { |
| 446 | answer = def_yn; |
| 447 | print_answer = 1; |
| 448 | } else if (ptr->flags & PR_LATCH_MASK) { |
| 449 | latch = PR_LATCH(ptr->flags); |
| 450 | if (!pr_latch[latch]) |
| 451 | pr_latch[latch] = |
| 452 | ask(prompt[(int) ptr->prompt], def_yn) + 1; |
| 453 | else |
| 454 | print_answer = 1; |
| 455 | answer = pr_latch[latch] - 1; |
| 456 | } else |
| 457 | answer = ask(prompt[(int) ptr->prompt], def_yn); |
| 458 | if (!answer && !(ptr->flags & PR_NO_OK)) |
| 459 | ext2fs_unmark_valid(fs); |
| 460 | |
| 461 | if (print_answer) |
| 462 | printf("%s.\n", |
| 463 | answer ? preen_msg[(int) ptr->prompt] : "IGNORED"); |
| 464 | |
| 465 | return answer; |
| 466 | } |