File: /usr/src/linux/scripts/cramfs/cramfsck.c
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
56 static const char *progname = "cramfsck";
57
58 static int fd; /* ROM image file descriptor */
59 static char *filename; /* ROM image filename */
60 struct cramfs_super *super; /* just find the cramfs superblock once */
61 static int opt_verbose = 0; /* 1 = verbose (-v), 2+ = very verbose (-vv) */
62 #ifdef INCLUDE_FS_TESTS
63 static int opt_extract = 0; /* extract cramfs (-x) */
64 char *extract_dir = NULL; /* extraction directory (-x) */
65
66 unsigned long start_inode = 1 << 28; /* start of first non-root inode */
67 unsigned long end_inode = 0; /* end of the directory structure */
68 unsigned long start_data = 1 << 28; /* start of the data (256 MB = max) */
69 unsigned long end_data = 0; /* end of the data */
70 /* true? cramfs_super < start_inode < end_inode <= start_data <= end_data */
71 static 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)
80 static char read_buffer[ROMBUFFERSIZE * 2];
81 static unsigned long read_buffer_block = ~0UL;
82
83 /* Uncompressing data structures... */
84 static char outbuffer[PAGE_CACHE_SIZE*2];
85 z_stream stream;
86
87 #endif /* INCLUDE_FS_TESTS */
88
89 /* Input status of 0 to print help and exit without an error. */
90 static 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
104 void 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 */
124 static 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
135 static 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
142 static struct cramfs_inode *iget(unsigned int ino)
143 {
144 return cramfs_iget(romfs_read(ino));
145 }
146
147 void 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 */
156 static 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
170 static 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
191 static 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
217 static 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
254 static 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
289 static 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
335 static 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 /* XXX - need to check end_inode for empty case? */
346 memcpy(newpath, path, pathlen);
347 newpath[pathlen] = '/';
348 pathlen++;
349 if (opt_verbose) {
350 print_node('d', inode, path);
351 }
352 if (opt_extract) {
353 mkdir(path, inode->mode);
354 change_file_status(path, inode);
355 }
356 while (count > 0) {
357 struct cramfs_inode *child = iget(offset);
358 int size;
359 int newlen = child->namelen << 2;
360
361 size = sizeof(struct cramfs_inode) + newlen;
362 count -= size;
363
364 offset += sizeof(struct cramfs_inode);
365
366 memcpy(newpath + pathlen, romfs_read(offset), newlen);
367 newpath[pathlen + newlen] = 0;
368 if ((pathlen + newlen) - strlen(newpath) > 3) {
369 fprintf(stderr, "%s: invalid cramfs--bad path length\n", filename);
370 exit(4);
371 }
372 expand_fs(strlen(newpath), newpath, child);
373
374 offset += newlen;
375
376 if (offset > end_inode) {
377 end_inode = offset;
378 }
379 }
380 return;
381 }
382 if (S_ISREG(inode->mode)) {
383 int fd = 0;
384 unsigned long offset = inode->offset << 2;
385
386 if (offset > 0 && offset < start_data) {
387 start_data = offset;
388 }
389 if (opt_verbose) {
390 print_node('f', inode, path);
391 }
392 if (opt_extract) {
393 fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, inode->mode);
394 }
395 if (inode->size) {
396 do_uncompress(fd, offset, inode->size);
397 }
398 if (opt_extract) {
399 close(fd);
400 change_file_status(path, inode);
401 }
402 return;
403 }
404 if (S_ISLNK(inode->mode)) {
405 unsigned long offset = inode->offset << 2;
406
407 if (offset < start_data) {
408 start_data = offset;
409 }
410 do_symlink(path, inode);
411 return;
412 }
413 else {
414 do_special_inode(path, inode);
415 return;
416 }
417 }
418 #endif /* INCLUDE_FS_TESTS */
419
420 int main(int argc, char **argv)
421 {
422 void *buf;
423 size_t length;
424 struct stat st;
425 u32 crc_old, crc_new;
426 #ifdef INCLUDE_FS_TESTS
427 struct cramfs_inode *root;
428 #endif /* INCLUDE_FS_TESTS */
429 int c; /* for getopt */
430 int start = 0;
431
432 if (argc)
433 progname = argv[0];
434
435 /* command line options */
436 while ((c = getopt(argc, argv, "hx:v")) != EOF) {
437 switch (c) {
438 case 'h':
439 usage(0);
440 case 'x':
441 #ifdef INCLUDE_FS_TESTS
442 opt_extract = 1;
443 extract_dir = malloc(strlen(optarg) + 1);
444 strcpy(extract_dir, optarg);
445 break;
446 #else /* not INCLUDE_FS_TESTS */
447 fprintf(stderr, "%s: compiled without -x support\n",
448 progname);
449 exit(16);
450 #endif /* not INCLUDE_FS_TESTS */
451 case 'v':
452 opt_verbose++;
453 break;
454 }
455 }
456
457 if ((argc - optind) != 1)
458 usage(16);
459 filename = argv[optind];
460
461 /* find the physical size of the file or block device */
462 if (lstat(filename, &st) < 0) {
463 perror(filename);
464 exit(8);
465 }
466 fd = open(filename, O_RDONLY);
467 if (fd < 0) {
468 perror(filename);
469 exit(8);
470 }
471 if (S_ISBLK(st.st_mode)) {
472 if (ioctl(fd, BLKGETSIZE, &length) < 0) {
473 fprintf(stderr, "%s: warning--unable to determine filesystem size \n", filename);
474 exit(4);
475 }
476 length = length * 512;
477 }
478 else if (S_ISREG(st.st_mode)) {
479 length = st.st_size;
480 }
481 else {
482 fprintf(stderr, "%s is not a block device or file\n", filename);
483 exit(8);
484 }
485
486 if (length < sizeof(struct cramfs_super)) {
487 fprintf(stderr, "%s: invalid cramfs--file length too short\n", filename);
488 exit(4);
489 }
490
491 if (S_ISBLK(st.st_mode)) {
492 /* nasty because mmap of block devices fails */
493 buf = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
494 read(fd, buf, length);
495 }
496 else {
497 /* nice and easy */
498 buf = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
499 }
500
501 /* XXX - this could be cleaner... */
502 if (((struct cramfs_super *) buf)->magic == CRAMFS_MAGIC) {
503 start = 0;
504 super = (struct cramfs_super *) buf;
505 }
506 else if (length >= (PAD_SIZE + sizeof(struct cramfs_super)) &&
507 ((((struct cramfs_super *) (buf + PAD_SIZE))->magic == CRAMFS_MAGIC)))
508 {
509 start = PAD_SIZE;
510 super = (struct cramfs_super *) (buf + PAD_SIZE);
511 }
512 else {
513 fprintf(stderr, "%s: invalid cramfs--wrong magic\n", filename);
514 exit(4);
515 }
516
517 if (super->flags & CRAMFS_FLAG_FSID_VERSION_2) {
518 /* length test */
519 if (length < super->size) {
520 fprintf(stderr, "%s: invalid cramfs--file length too short\n", filename);
521 exit(4);
522 }
523 else if (length > super->size) {
524 fprintf(stderr, "%s: warning--file length too long, padded image?\n", filename);
525 }
526
527 /* CRC test */
528 crc_old = super->fsid.crc;
529 super->fsid.crc = crc32(0L, Z_NULL, 0);
530 crc_new = crc32(0L, Z_NULL, 0);
531 crc_new = crc32(crc_new, (unsigned char *) buf+start, super->size - start);
532 if (crc_new != crc_old) {
533 fprintf(stderr, "%s: invalid cramfs--crc error\n", filename);
534 exit(4);
535 }
536 }
537 else {
538 fprintf(stderr, "%s: warning--old cramfs image, no CRC\n",
539 filename);
540 }
541
542 #ifdef INCLUDE_FS_TESTS
543 super = (struct cramfs_super *) malloc(sizeof(struct cramfs_super));
544 if (((struct cramfs_super *) buf)->magic == CRAMFS_MAGIC) {
545 memcpy(super, buf, sizeof(struct cramfs_super));
546 }
547 else if (length >= (PAD_SIZE + sizeof(struct cramfs_super)) &&
548 ((((struct cramfs_super *) (buf + PAD_SIZE))->magic == CRAMFS_MAGIC)))
549 {
550 memcpy(super, (buf + PAD_SIZE), sizeof(struct cramfs_super));
551 }
552
553 munmap(buf, length);
554
555 /* file format test, uses fake "blocked" accesses */
556 root = read_super();
557 umask(0);
558 euid = geteuid();
559 if (!root) {
560 fprintf(stderr, "%s: invalid cramfs--bad superblock\n",
561 filename);
562 exit(4);
563 }
564 stream.next_in = NULL;
565 stream.avail_in = 0;
566 inflateInit(&stream);
567
568 if (!extract_dir) {
569 extract_dir = "root";
570 }
571
572 expand_fs(strlen(extract_dir), extract_dir, root);
573 inflateEnd(&stream);
574
575 if (start_data != 1 << 28 && end_inode != start_data) {
576 fprintf(stderr, "%s: invalid cramfs--directory data end (%ld) != file data start (%ld)\n", filename, end_inode, start_data);
577 exit(4);
578 }
579 if (super->flags & CRAMFS_FLAG_FSID_VERSION_2) {
580 if (end_data > super->size) {
581 fprintf(stderr, "%s: invalid cramfs--invalid file data offset\n", filename);
582 exit(4);
583 }
584 }
585 #endif /* INCLUDE_FS_TESTS */
586
587 exit(0);
588 }
589