1/* 2 * cramfsck - check a cramfs file system 3 * 4 * Copyright (C) 2000-2001 Transmeta Corporation 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or 9 * (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program; if not, write to the Free Software 18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 * 20 * 1999/12/03: Linus Torvalds (cramfs tester and unarchive program) 21 * 2000/06/03: Daniel Quinlan (CRC and length checking program) 22 * 2000/06/04: Daniel Quinlan (merged programs, added options, support 23 * for special files, preserve permissions and 24 * ownership, cramfs superblock v2, bogus mode 25 * test, pathname length test, etc.) 26 * 2000/06/06: Daniel Quinlan (support for holes, pretty-printing, 27 * symlink size test) 28 * 2000/07/11: Daniel Quinlan (file length tests, start at offset 0 or 512, 29 * fsck-compatible exit codes) 30 * 2000/07/15: Daniel Quinlan (initial support for block devices) 31 */ 32 33/* compile-time options */ 34#define INCLUDE_FS_TESTS /* include cramfs checking and extraction */ 35 36#include <sys/types.h> 37#include <stdio.h> 38#include <sys/stat.h> 39#include <unistd.h> 40#include <sys/mman.h> 41#include <sys/fcntl.h> 42#include <dirent.h> 43#include <stdlib.h> 44#include <errno.h> 45#include <string.h> 46#include <assert.h> 47#include <getopt.h> 48#include <sys/sysmacros.h> 49#include <utime.h> 50#include <sys/ioctl.h> 51#define _LINUX_STRING_H_ 52#include <linux/fs.h> 53#include <linux/cramfs_fs.h> 54#include <zlib.h> 55 56static const char *progname = "cramfsck"; 57 58static int fd; /* ROM image file descriptor */ 59static char *filename; /* ROM image filename */ 60struct cramfs_super *super; /* just find the cramfs superblock once */ 61static int opt_verbose = 0; /* 1 = verbose (-v), 2+ = very verbose (-vv) */ 62#ifdef INCLUDE_FS_TESTS 63static int opt_extract = 0; /* extract cramfs (-x) */ 64char *extract_dir = NULL; /* extraction directory (-x) */ 65 66unsigned long start_inode = 1 << 28; /* start of first non-root inode */ 67unsigned long end_inode = 0; /* end of the directory structure */ 68unsigned long start_data = 1 << 28; /* start of the data (256 MB = max) */ 69unsigned long end_data = 0; /* end of the data */ 70/* true? cramfs_super < start_inode < end_inode <= start_data <= end_data */ 71static uid_t euid; /* effective UID */ 72 73#define PAD_SIZE 512 74#define PAGE_CACHE_SIZE (4096) 75 76/* Guarantee access to at least 8kB at a time */ 77#define ROMBUFFER_BITS 13 78#define ROMBUFFERSIZE (1 << ROMBUFFER_BITS) 79#define ROMBUFFERMASK (ROMBUFFERSIZE-1) 80static char read_buffer[ROMBUFFERSIZE * 2]; 81static unsigned long read_buffer_block = ~0UL; 82 83/* Uncompressing data structures... */ 84static char outbuffer[PAGE_CACHE_SIZE*2]; 85z_stream stream; 86 87#endif /* INCLUDE_FS_TESTS */ 88 89/* Input status of 0 to print help and exit without an error. */ 90static void usage(int status) 91{ 92 FILE *stream = status ? stderr : stdout; 93 94 fprintf(stream, "usage: %s [-hv] [-x dir] file\n" 95 " -h print this help\n" 96 " -x dir extract into dir\n" 97 " -v be more verbose\n" 98 " file file to test\n", progname); 99 100 exit(status); 101} 102 103#ifdef INCLUDE_FS_TESTS 104void print_node(char type, struct cramfs_inode *i, char *name) 105{ 106 char info[10]; 107 108 if (S_ISCHR(i->mode) || (S_ISBLK(i->mode))) { 109 /* major/minor numbers can be as high as 2^12 or 4096 */ 110 snprintf(info, 10, "%4d,%4d", major(i->size), minor(i->size)); 111 } 112 else { 113 /* size be as high as 2^24 or 16777216 */ 114 snprintf(info, 10, "%9d", i->size); 115 } 116 117 printf("%c %04o %s %5d:%-3d %s\n", 118 type, i->mode & ~S_IFMT, info, i->uid, i->gid, name); 119} 120 121/* 122 * Create a fake "blocked" access 123 */ 124static void *romfs_read(unsigned long offset) 125{ 126 unsigned int block = offset >> ROMBUFFER_BITS; 127 if (block != read_buffer_block) { 128 read_buffer_block = block; 129 lseek(fd, block << ROMBUFFER_BITS, SEEK_SET); 130 read(fd, read_buffer, ROMBUFFERSIZE * 2); 131 } 132 return read_buffer + (offset & ROMBUFFERMASK); 133} 134 135static struct cramfs_inode *cramfs_iget(struct cramfs_inode * i) 136{ 137 struct cramfs_inode *inode = malloc(sizeof(struct cramfs_inode)); 138 *inode = *i; 139 return inode; 140} 141 142static struct cramfs_inode *iget(unsigned int ino) 143{ 144 return cramfs_iget(romfs_read(ino)); 145} 146 147void iput(struct cramfs_inode *inode) 148{ 149 free(inode); 150} 151 152/* 153 * Return the offset of the root directory, 154 * or 0 if none. 155 */ 156static struct cramfs_inode *read_super(void) 157{ 158 unsigned long offset; 159 160 offset = super->root.offset << 2; 161 if (super->magic != CRAMFS_MAGIC) 162 return NULL; 163 if (memcmp(super->signature, CRAMFS_SIGNATURE, sizeof(super->signature)) != 0) 164 return NULL; 165 if (offset < sizeof(super)) 166 return NULL; 167 return cramfs_iget(&super->root); 168} 169 170static int uncompress_block(void *src, int len) 171{ 172 int err; 173 174 stream.next_in = src; 175 stream.avail_in = len; 176 177 stream.next_out = (unsigned char *) outbuffer; 178 stream.avail_out = PAGE_CACHE_SIZE*2; 179 180 inflateReset(&stream); 181 182 err = inflate(&stream, Z_FINISH); 183 if (err != Z_STREAM_END) { 184 fprintf(stderr, "%s: error %d while decompressing! %p(%d)\n", 185 filename, err, src, len); 186 exit(4); 187 } 188 return stream.total_out; 189} 190 191static void change_file_status(char *path, struct cramfs_inode *i) 192{ 193 struct utimbuf epoch = { 0, 0 }; 194 195 if (euid == 0) { 196 if (lchown(path, i->uid, i->gid) < 0) { 197 perror(path); 198 exit(8); 199 } 200 if (S_ISLNK(i->mode)) 201 return; 202 if ((S_ISUID | S_ISGID) & i->mode) { 203 if (chmod(path, i->mode) < 0) { 204 perror(path); 205 exit(8); 206 } 207 } 208 } 209 if (S_ISLNK(i->mode)) 210 return; 211 if (utime(path, &epoch) < 0) { 212 perror(path); 213 exit(8); 214 } 215} 216 217static void do_symlink(char *path, struct cramfs_inode *i) 218{ 219 unsigned long offset = i->offset << 2; 220 unsigned long curr = offset + 4; 221 unsigned long next = *(u32 *) romfs_read(offset); 222 unsigned long size; 223 224 if (next > end_data) { 225 end_data = next; 226 } 227 228 size = uncompress_block(romfs_read(curr), next - curr); 229 if (size != i->size) { 230 fprintf(stderr, "%s: size error in symlink `%s'\n", 231 filename, path); 232 exit(4); 233 } 234 outbuffer[size] = 0; 235 if (opt_verbose) { 236 char *str; 237 238 str = malloc(strlen(outbuffer) + strlen(path) + 5); 239 strcpy(str, path); 240 strncat(str, " -> ", 4); 241 strncat(str, outbuffer, size); 242 243 print_node('l', i, str); 244 if (opt_verbose > 1) { 245 printf(" uncompressing block at %ld to %ld (%ld)\n", curr, next, next - curr); 246 } 247 } 248 if (opt_extract) { 249 symlink(outbuffer, path); 250 change_file_status(path, i); 251 } 252} 253 254static void do_special_inode(char *path, struct cramfs_inode *i) 255{ 256 dev_t devtype = 0; 257 char type; 258 259 if (S_ISCHR(i->mode)) { 260 devtype = i->size; 261 type = 'c'; 262 } 263 else if (S_ISBLK(i->mode)) { 264 devtype = i->size; 265 type = 'b'; 266 } 267 else if (S_ISFIFO(i->mode)) 268 type = 'p'; 269 else if (S_ISSOCK(i->mode)) 270 type = 's'; 271 else { 272 fprintf(stderr, "%s: bogus mode on `%s' (%o)\n", filename, path, i->mode); 273 exit(4); 274 } 275 276 if (opt_verbose) { 277 print_node(type, i, path); 278 } 279 280 if (opt_extract) { 281 if (mknod(path, i->mode, devtype) < 0) { 282 perror(path); 283 exit(8); 284 } 285 change_file_status(path, i); 286 } 287} 288 289static void do_uncompress(int fd, unsigned long offset, unsigned long size) 290{ 291 unsigned long curr = offset + 4 * ((size + PAGE_CACHE_SIZE - 1) / PAGE_CACHE_SIZE); 292 293 do { 294 unsigned long out = PAGE_CACHE_SIZE; 295 unsigned long next = *(u32 *) romfs_read(offset); 296 297 if (next > end_data) { 298 end_data = next; 299 } 300 301 offset += 4; 302 if (curr == next) { 303 if (opt_verbose > 1) { 304 printf(" hole at %ld (%d)\n", curr, PAGE_CACHE_SIZE); 305 } 306 if (size < PAGE_CACHE_SIZE) 307 out = size; 308 memset(outbuffer, 0x00, out); 309 } 310 else { 311 if (opt_verbose > 1) { 312 printf(" uncompressing block at %ld to %ld (%ld)\n", curr, next, next - curr); 313 } 314 out = uncompress_block(romfs_read(curr), next - curr); 315 } 316 if (size >= PAGE_CACHE_SIZE) { 317 if (out != PAGE_CACHE_SIZE) { 318 fprintf(stderr, "%s: Non-block (%ld) bytes\n", filename, out); 319 exit(4); 320 } 321 } else { 322 if (out != size) { 323 fprintf(stderr, "%s: Non-size (%ld vs %ld) bytes\n", filename, out, size); 324 exit(4); 325 } 326 } 327 size -= out; 328 if (opt_extract) { 329 write(fd, outbuffer, out); 330 } 331 curr = next; 332 } while (size); 333} 334 335static void expand_fs(int pathlen, char *path, struct cramfs_inode *inode) 336{ 337 if (S_ISDIR(inode->mode)) { 338 int count = inode->size; 339 unsigned long offset = inode->offset << 2; 340 char *newpath = malloc(pathlen + 256); 341 342 if (count > 0 && offset < start_inode) { 343 start_inode = offset; 344 } 345 memcpy(newpath, path, pathlen); 346 newpath[pathlen] = '/'; 347 pathlen++; 348 if (opt_verbose) { 349 print_node('d', inode, path); 350 } 351 if (opt_extract) { 352 mkdir(path, inode->mode); 353 change_file_status(path, inode); 354 } 355 while (count > 0) { 356 struct cramfs_inode *child = iget(offset); 357 int size; 358 int newlen = child->namelen << 2; 359 360 size = sizeof(struct cramfs_inode) + newlen; 361 count -= size; 362 363 offset += sizeof(struct cramfs_inode); 364 365 memcpy(newpath + pathlen, romfs_read(offset), newlen); 366 newpath[pathlen + newlen] = 0; 367 if ((pathlen + newlen) - strlen(newpath) > 3) { 368 fprintf(stderr, "%s: invalid cramfs--bad path length\n", filename); 369 exit(4); 370 } 371 expand_fs(strlen(newpath), newpath, child); 372 373 offset += newlen; 374 375 if (offset > end_inode) { 376 end_inode = offset; 377 } 378 } 379 return; 380 } 381 if (S_ISREG(inode->mode)) { 382 int fd = 0; 383 unsigned long offset = inode->offset << 2; 384 385 if (offset > 0 && offset < start_data) { 386 start_data = offset; 387 } 388 if (opt_verbose) { 389 print_node('f', inode, path); 390 } 391 if (opt_extract) { 392 fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, inode->mode); 393 } 394 if (inode->size) { 395 do_uncompress(fd, offset, inode->size); 396 } 397 if (opt_extract) { 398 close(fd); 399 change_file_status(path, inode); 400 } 401 return; 402 } 403 if (S_ISLNK(inode->mode)) { 404 unsigned long offset = inode->offset << 2; 405 406 if (offset < start_data) { 407 start_data = offset; 408 } 409 do_symlink(path, inode); 410 return; 411 } 412 else { 413 do_special_inode(path, inode); 414 return; 415 } 416} 417#endif /* INCLUDE_FS_TESTS */ 418 419int main(int argc, char **argv) 420{ 421 void *buf; 422 size_t length; 423 struct stat st; 424 u32 crc_old, crc_new; 425#ifdef INCLUDE_FS_TESTS 426 struct cramfs_inode *root; 427#endif /* INCLUDE_FS_TESTS */ 428 int c; /* for getopt */ 429 int start = 0; 430 431 if (argc) 432 progname = argv[0]; 433 434 /* command line options */ 435 while ((c = getopt(argc, argv, "hx:v")) != EOF) { 436 switch (c) { 437 case 'h': 438 usage(0); 439 case 'x': 440#ifdef INCLUDE_FS_TESTS 441 opt_extract = 1; 442 extract_dir = malloc(strlen(optarg) + 1); 443 strcpy(extract_dir, optarg); 444 break; 445#else /* not INCLUDE_FS_TESTS */ 446 fprintf(stderr, "%s: compiled without -x support\n", 447 progname); 448 exit(16); 449#endif /* not INCLUDE_FS_TESTS */ 450 case 'v': 451 opt_verbose++; 452 break; 453 } 454 } 455 456 if ((argc - optind) != 1) 457 usage(16); 458 filename = argv[optind]; 459 460 /* find the physical size of the file or block device */ 461 if (lstat(filename, &st) < 0) { 462 perror(filename); 463 exit(8); 464 } 465 fd = open(filename, O_RDONLY); 466 if (fd < 0) { 467 perror(filename); 468 exit(8); 469 } 470 if (S_ISBLK(st.st_mode)) { 471 if (ioctl(fd, BLKGETSIZE, &length) < 0) { 472 fprintf(stderr, "%s: warning--unable to determine filesystem size \n", filename); 473 exit(4); 474 } 475 length = length * 512; 476 } 477 else if (S_ISREG(st.st_mode)) { 478 length = st.st_size; 479 } 480 else { 481 fprintf(stderr, "%s is not a block device or file\n", filename); 482 exit(8); 483 } 484 485 if (length < sizeof(struct cramfs_super)) { 486 fprintf(stderr, "%s: invalid cramfs--file length too short\n", filename); 487 exit(4); 488 } 489 490 if (S_ISBLK(st.st_mode)) { 491 /* nasty because mmap of block devices fails */ 492 buf = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 493 read(fd, buf, length); 494 } 495 else { 496 /* nice and easy */ 497 buf = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); 498 } 499 500 if (((struct cramfs_super *) buf)->magic == CRAMFS_MAGIC) { 501 start = 0; 502 super = (struct cramfs_super *) buf; 503 } 504 else if (length >= (PAD_SIZE + sizeof(struct cramfs_super)) && 505 ((((struct cramfs_super *) (buf + PAD_SIZE))->magic == CRAMFS_MAGIC))) 506 { 507 start = PAD_SIZE; 508 super = (struct cramfs_super *) (buf + PAD_SIZE); 509 } 510 else { 511 fprintf(stderr, "%s: invalid cramfs--wrong magic\n", filename); 512 exit(4); 513 } 514 515 if (super->flags & CRAMFS_FLAG_FSID_VERSION_2) { 516 /* length test */ 517 if (length < super->size) { 518 fprintf(stderr, "%s: invalid cramfs--file length too short\n", filename); 519 exit(4); 520 } 521 else if (length > super->size) { 522 fprintf(stderr, "%s: warning--file length too long, padded image?\n", filename); 523 } 524 525 /* CRC test */ 526 crc_old = super->fsid.crc; 527 super->fsid.crc = crc32(0L, Z_NULL, 0); 528 crc_new = crc32(0L, Z_NULL, 0); 529 crc_new = crc32(crc_new, (unsigned char *) buf+start, super->size - start); 530 if (crc_new != crc_old) { 531 fprintf(stderr, "%s: invalid cramfs--crc error\n", filename); 532 exit(4); 533 } 534 } 535 else { 536 fprintf(stderr, "%s: warning--old cramfs image, no CRC\n", 537 filename); 538 } 539 540#ifdef INCLUDE_FS_TESTS 541 super = (struct cramfs_super *) malloc(sizeof(struct cramfs_super)); 542 if (((struct cramfs_super *) buf)->magic == CRAMFS_MAGIC) { 543 memcpy(super, buf, sizeof(struct cramfs_super)); 544 } 545 else if (length >= (PAD_SIZE + sizeof(struct cramfs_super)) && 546 ((((struct cramfs_super *) (buf + PAD_SIZE))->magic == CRAMFS_MAGIC))) 547 { 548 memcpy(super, (buf + PAD_SIZE), sizeof(struct cramfs_super)); 549 } 550 551 munmap(buf, length); 552 553 /* file format test, uses fake "blocked" accesses */ 554 root = read_super(); 555 umask(0); 556 euid = geteuid(); 557 if (!root) { 558 fprintf(stderr, "%s: invalid cramfs--bad superblock\n", 559 filename); 560 exit(4); 561 } 562 stream.next_in = NULL; 563 stream.avail_in = 0; 564 inflateInit(&stream); 565 566 if (!extract_dir) { 567 extract_dir = "root"; 568 } 569 570 expand_fs(strlen(extract_dir), extract_dir, root); 571 inflateEnd(&stream); 572 573 if (start_data != 1 << 28 && end_inode != start_data) { 574 fprintf(stderr, "%s: invalid cramfs--directory data end (%ld) != file data start (%ld)\n", filename, end_inode, start_data); 575 exit(4); 576 } 577 if (super->flags & CRAMFS_FLAG_FSID_VERSION_2) { 578 if (end_data > super->size) { 579 fprintf(stderr, "%s: invalid cramfs--invalid file data offset\n", filename); 580 exit(4); 581 } 582 } 583#endif /* INCLUDE_FS_TESTS */ 584 585 exit(0); 586} 587