]>
Commit | Line | Data |
---|---|---|
63cccae4 KZ |
1 | /* |
2 | * cramfsck - check a cramfs file system | |
3 | * | |
19922f22 KZ |
4 | * Copyright (C) 2000-2002 Transmeta Corporation |
5 | * 2005 Adrian Bunk | |
63cccae4 KZ |
6 | * |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License as published by | |
9 | * the Free Software Foundation; either version 2 of the License, or | |
10 | * (at your option) any later version. | |
11 | * | |
12 | * This program is distributed in the hope that it will be useful, | |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | * GNU General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU General Public License | |
18 | * along with this program; if not, write to the Free Software | |
19 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
20 | * | |
21 | * 1999/12/03: Linus Torvalds (cramfs tester and unarchive program) | |
22 | * 2000/06/03: Daniel Quinlan (CRC and length checking program) | |
23 | * 2000/06/04: Daniel Quinlan (merged programs, added options, support | |
24 | * for special files, preserve permissions and | |
25 | * ownership, cramfs superblock v2, bogus mode | |
26 | * test, pathname length test, etc.) | |
27 | * 2000/06/06: Daniel Quinlan (support for holes, pretty-printing, | |
28 | * symlink size test) | |
29 | * 2000/07/11: Daniel Quinlan (file length tests, start at offset 0 or 512, | |
30 | * fsck-compatible exit codes) | |
31 | * 2000/07/15: Daniel Quinlan (initial support for block devices) | |
19922f22 KZ |
32 | * 2002/01/10: Daniel Quinlan (additional checks, test more return codes, |
33 | * use read if mmap fails, standardize messages) | |
63cccae4 KZ |
34 | */ |
35 | ||
36 | /* compile-time options */ | |
19922f22 | 37 | //#define INCLUDE_FS_TESTS /* include cramfs checking and extraction */ |
63cccae4 KZ |
38 | |
39 | #include <stdio.h> | |
19922f22 | 40 | #include <stdarg.h> |
63cccae4 KZ |
41 | #include <unistd.h> |
42 | #include <dirent.h> | |
43 | #include <stdlib.h> | |
44 | #include <errno.h> | |
45 | #include <string.h> | |
46 | #include <getopt.h> | |
47 | #include <utime.h> | |
48 | #include <fcntl.h> | |
49 | #include <zlib.h> | |
50 | ||
51 | #include <sys/types.h> | |
52 | #include <sys/stat.h> | |
53 | #include <sys/mman.h> | |
54 | #include <sys/ioctl.h> | |
55 | #include <sys/sysmacros.h> /* for major, minor */ | |
56 | ||
57 | #include "cramfs.h" | |
fbaec83b | 58 | #include "cramfs_common.h" |
63cccae4 | 59 | #include "nls.h" |
098fa6b1 | 60 | #include "blkdev.h" |
63cccae4 KZ |
61 | |
62 | static const char *progname = "cramfsck"; | |
63 | ||
64 | static int fd; /* ROM image file descriptor */ | |
65 | static char *filename; /* ROM image filename */ | |
19922f22 | 66 | struct cramfs_super super; /* just find the cramfs superblock once */ |
fbaec83b | 67 | static int cramfs_is_big_endian = 0; /* source is big endian */ |
63cccae4 KZ |
68 | static int opt_verbose = 0; /* 1 = verbose (-v), 2+ = very verbose (-vv) */ |
69 | ||
63cccae4 KZ |
70 | char *extract_dir = NULL; /* extraction directory (-x) */ |
71 | ||
19922f22 KZ |
72 | /* Exit codes used by fsck-type programs */ |
73 | #define FSCK_OK 0 /* No errors */ | |
74 | #define FSCK_NONDESTRUCT 1 /* File system errors corrected */ | |
75 | #define FSCK_REBOOT 2 /* System should be rebooted */ | |
76 | #define FSCK_UNCORRECTED 4 /* File system errors left uncorrected */ | |
77 | #define FSCK_ERROR 8 /* Operational error */ | |
78 | #define FSCK_USAGE 16 /* Usage or syntax error */ | |
79 | #define FSCK_LIBRARY 128 /* Shared library error */ | |
63cccae4 KZ |
80 | |
81 | #define PAD_SIZE 512 | |
b22550fa | 82 | |
19922f22 KZ |
83 | #ifdef INCLUDE_FS_TESTS |
84 | ||
85 | static int opt_extract = 0; /* extract cramfs (-x) */ | |
86 | ||
87 | static uid_t euid; /* effective UID */ | |
88 | ||
89 | /* (cramfs_super + start) <= start_dir < end_dir <= start_data <= end_data */ | |
90 | static unsigned long start_dir = ~0UL; /* start of first non-root inode */ | |
91 | static unsigned long end_dir = 0; /* end of the directory structure */ | |
92 | static unsigned long start_data = ~0UL; /* start of the data (256 MB = max) */ | |
93 | static unsigned long end_data = 0; /* end of the data */ | |
94 | ||
63cccae4 KZ |
95 | |
96 | /* Guarantee access to at least 8kB at a time */ | |
97 | #define ROMBUFFER_BITS 13 | |
98 | #define ROMBUFFERSIZE (1 << ROMBUFFER_BITS) | |
99 | #define ROMBUFFERMASK (ROMBUFFERSIZE-1) | |
100 | static char read_buffer[ROMBUFFERSIZE * 2]; | |
101 | static unsigned long read_buffer_block = ~0UL; | |
102 | ||
19922f22 | 103 | static z_stream stream; |
63cccae4 | 104 | |
19922f22 KZ |
105 | /* Prototypes */ |
106 | static void expand_fs(char *, struct cramfs_inode *); | |
63cccae4 KZ |
107 | #endif /* INCLUDE_FS_TESTS */ |
108 | ||
19922f22 KZ |
109 | static char *outbuffer; |
110 | ||
111 | static size_t page_size; | |
112 | ||
63cccae4 KZ |
113 | /* Input status of 0 to print help and exit without an error. */ |
114 | static void usage(int status) | |
115 | { | |
116 | FILE *stream = status ? stderr : stdout; | |
117 | ||
118 | fprintf(stream, _("usage: %s [-hv] [-x dir] file\n" | |
119 | " -h print this help\n" | |
120 | " -x dir extract into dir\n" | |
121 | " -v be more verbose\n" | |
122 | " file file to test\n"), progname); | |
123 | ||
124 | exit(status); | |
125 | } | |
126 | ||
19922f22 KZ |
127 | static void die(int status, int syserr, const char *fmt, ...) |
128 | { | |
129 | va_list arg_ptr; | |
130 | int save = errno; | |
131 | ||
132 | fflush(0); | |
133 | va_start(arg_ptr, fmt); | |
134 | fprintf(stderr, "%s: ", progname); | |
135 | vfprintf(stderr, fmt, arg_ptr); | |
136 | if (syserr) { | |
137 | fprintf(stderr, ": %s", strerror(save)); | |
138 | } | |
139 | fprintf(stderr, "\n"); | |
140 | va_end(arg_ptr); | |
141 | exit(status); | |
142 | } | |
143 | ||
fbaec83b SRP |
144 | int get_superblock_endianness(u32 magic) |
145 | { | |
146 | if (magic == CRAMFS_MAGIC) { | |
147 | cramfs_is_big_endian = HOST_IS_BIG_ENDIAN; | |
148 | return 0; | |
149 | } | |
150 | else if (magic == u32_toggle_endianness(!HOST_IS_BIG_ENDIAN, CRAMFS_MAGIC)) { | |
151 | cramfs_is_big_endian = !HOST_IS_BIG_ENDIAN; | |
152 | return 0; | |
153 | } | |
154 | else { | |
155 | return -1; | |
156 | } | |
157 | } | |
158 | ||
19922f22 KZ |
159 | static void test_super(int *start, size_t *length) { |
160 | struct stat st; | |
161 | ||
162 | /* find the physical size of the file or block device */ | |
163 | if (stat(filename, &st) < 0) { | |
e56644ca | 164 | die(FSCK_ERROR, 1, _("stat failed: %s"), filename); |
19922f22 KZ |
165 | } |
166 | fd = open(filename, O_RDONLY); | |
167 | if (fd < 0) { | |
e56644ca | 168 | die(FSCK_ERROR, 1, _("open failed: %s"), filename); |
19922f22 KZ |
169 | } |
170 | if (S_ISBLK(st.st_mode)) { | |
098fa6b1 ST |
171 | unsigned long long bytes; |
172 | if (blkdev_get_size(fd, &bytes)) { | |
e56644ca | 173 | die(FSCK_ERROR, 1, _("ioctl failed: unable to determine device size: %s"), filename); |
19922f22 | 174 | } |
098fa6b1 | 175 | *length = bytes; |
19922f22 KZ |
176 | } |
177 | else if (S_ISREG(st.st_mode)) { | |
178 | *length = st.st_size; | |
179 | } | |
180 | else { | |
e56644ca | 181 | die(FSCK_ERROR, 0, _("not a block device or file: %s"), filename); |
19922f22 KZ |
182 | } |
183 | ||
184 | if (*length < sizeof(struct cramfs_super)) { | |
e56644ca | 185 | die(FSCK_UNCORRECTED, 0, _("file length too short")); |
19922f22 KZ |
186 | } |
187 | ||
188 | /* find superblock */ | |
189 | if (read(fd, &super, sizeof(super)) != sizeof(super)) { | |
e56644ca | 190 | die(FSCK_ERROR, 1, _("read failed: %s"), filename); |
19922f22 | 191 | } |
fbaec83b | 192 | if (get_superblock_endianness(super.magic) != -1) { |
19922f22 KZ |
193 | *start = 0; |
194 | } | |
195 | else if (*length >= (PAD_SIZE + sizeof(super))) { | |
196 | lseek(fd, PAD_SIZE, SEEK_SET); | |
197 | if (read(fd, &super, sizeof(super)) != sizeof(super)) { | |
e56644ca | 198 | die(FSCK_ERROR, 1, _("read failed: %s"), filename); |
19922f22 | 199 | } |
fbaec83b | 200 | if (get_superblock_endianness(super.magic) != -1) { |
19922f22 KZ |
201 | *start = PAD_SIZE; |
202 | } | |
fbaec83b SRP |
203 | else { |
204 | die(FSCK_UNCORRECTED, 0, "superblock magic not found"); | |
205 | } | |
19922f22 | 206 | } |
fbaec83b | 207 | else { |
e56644ca | 208 | die(FSCK_UNCORRECTED, 0, _("superblock magic not found")); |
19922f22 | 209 | } |
fbaec83b SRP |
210 | |
211 | if (opt_verbose) { | |
212 | printf("cramfs endianness is %s\n", cramfs_is_big_endian ? "big" : "little"); | |
213 | } | |
214 | ||
215 | super_toggle_endianness(cramfs_is_big_endian, &super); | |
19922f22 | 216 | if (super.flags & ~CRAMFS_SUPPORTED_FLAGS) { |
e56644ca | 217 | die(FSCK_ERROR, 0, _("unsupported filesystem features")); |
19922f22 KZ |
218 | } |
219 | if (super.size < page_size) { | |
e56644ca | 220 | die(FSCK_UNCORRECTED, 0, _("superblock size (%d) too small"), super.size); |
19922f22 KZ |
221 | } |
222 | if (super.flags & CRAMFS_FLAG_FSID_VERSION_2) { | |
223 | if (super.fsid.files == 0) { | |
e56644ca | 224 | die(FSCK_UNCORRECTED, 0, _("zero file count")); |
19922f22 KZ |
225 | } |
226 | if (*length < super.size) { | |
e56644ca | 227 | die(FSCK_UNCORRECTED, 0, _("file length too short")); |
19922f22 KZ |
228 | } |
229 | else if (*length > super.size) { | |
e56644ca | 230 | fprintf(stderr, _("warning: file extends past end of filesystem\n")); |
19922f22 KZ |
231 | } |
232 | } | |
233 | else { | |
e56644ca | 234 | fprintf(stderr, _("warning: old cramfs format\n")); |
19922f22 KZ |
235 | } |
236 | } | |
237 | ||
238 | static void test_crc(int start) | |
239 | { | |
240 | void *buf; | |
241 | u32 crc; | |
242 | ||
243 | if (!(super.flags & CRAMFS_FLAG_FSID_VERSION_2)) { | |
244 | #ifdef INCLUDE_FS_TESTS | |
245 | return; | |
246 | #else /* not INCLUDE_FS_TESTS */ | |
e56644ca | 247 | die(FSCK_USAGE, 0, _("unable to test CRC: old cramfs format")); |
19922f22 KZ |
248 | #endif /* not INCLUDE_FS_TESTS */ |
249 | } | |
250 | ||
251 | crc = crc32(0L, Z_NULL, 0); | |
252 | ||
253 | buf = mmap(NULL, super.size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); | |
254 | if (buf == MAP_FAILED) { | |
255 | buf = mmap(NULL, super.size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); | |
256 | if (buf != MAP_FAILED) { | |
257 | lseek(fd, 0, SEEK_SET); | |
5f841647 | 258 | if (read(fd, buf, super.size) < 0) |
e56644ca | 259 | die(FSCK_ERROR, 1, _("read failed: %s"), filename); |
19922f22 KZ |
260 | } |
261 | } | |
262 | if (buf != MAP_FAILED) { | |
263 | ((struct cramfs_super *) (buf+start))->fsid.crc = crc32(0L, Z_NULL, 0); | |
264 | crc = crc32(crc, buf+start, super.size-start); | |
265 | munmap(buf, super.size); | |
266 | } | |
267 | else { | |
268 | int retval; | |
269 | size_t length = 0; | |
270 | ||
271 | buf = malloc(4096); | |
272 | if (!buf) { | |
e56644ca | 273 | die(FSCK_ERROR, 1, _("malloc failed")); |
19922f22 KZ |
274 | } |
275 | lseek(fd, start, SEEK_SET); | |
276 | for (;;) { | |
277 | retval = read(fd, buf, 4096); | |
278 | if (retval < 0) { | |
e56644ca | 279 | die(FSCK_ERROR, 1, _("read failed: %s"), filename); |
19922f22 KZ |
280 | } |
281 | else if (retval == 0) { | |
282 | break; | |
283 | } | |
284 | if (length == 0) { | |
285 | ((struct cramfs_super *) buf)->fsid.crc = crc32(0L, Z_NULL, 0); | |
286 | } | |
287 | length += retval; | |
288 | if (length > (super.size-start)) { | |
289 | crc = crc32(crc, buf, retval - (length - (super.size-start))); | |
290 | break; | |
291 | } | |
292 | crc = crc32(crc, buf, retval); | |
293 | } | |
294 | free(buf); | |
295 | } | |
296 | ||
297 | if (crc != super.fsid.crc) { | |
e56644ca | 298 | die(FSCK_UNCORRECTED, 0, _("crc error")); |
19922f22 KZ |
299 | } |
300 | } | |
301 | ||
63cccae4 KZ |
302 | #ifdef INCLUDE_FS_TESTS |
303 | static void print_node(char type, struct cramfs_inode *i, char *name) | |
304 | { | |
305 | char info[10]; | |
306 | ||
307 | if (S_ISCHR(i->mode) || (S_ISBLK(i->mode))) { | |
308 | /* major/minor numbers can be as high as 2^12 or 4096 */ | |
309 | snprintf(info, 10, "%4d,%4d", major(i->size), minor(i->size)); | |
310 | } | |
311 | else { | |
312 | /* size be as high as 2^24 or 16777216 */ | |
313 | snprintf(info, 10, "%9d", i->size); | |
314 | } | |
315 | ||
316 | printf("%c %04o %s %5d:%-3d %s\n", | |
317 | type, i->mode & ~S_IFMT, info, i->uid, i->gid, name); | |
318 | } | |
319 | ||
320 | /* | |
321 | * Create a fake "blocked" access | |
322 | */ | |
323 | static void *romfs_read(unsigned long offset) | |
324 | { | |
325 | unsigned int block = offset >> ROMBUFFER_BITS; | |
326 | if (block != read_buffer_block) { | |
327 | read_buffer_block = block; | |
328 | lseek(fd, block << ROMBUFFER_BITS, SEEK_SET); | |
329 | read(fd, read_buffer, ROMBUFFERSIZE * 2); | |
330 | } | |
331 | return read_buffer + (offset & ROMBUFFERMASK); | |
332 | } | |
333 | ||
334 | static struct cramfs_inode *cramfs_iget(struct cramfs_inode * i) | |
335 | { | |
336 | struct cramfs_inode *inode = malloc(sizeof(struct cramfs_inode)); | |
19922f22 KZ |
337 | |
338 | if (!inode) { | |
e56644ca | 339 | die(FSCK_ERROR, 1, _("malloc failed")); |
19922f22 | 340 | } |
fbaec83b | 341 | inode_to_host(cramfs_is_big_endian, i, inode); |
63cccae4 KZ |
342 | return inode; |
343 | } | |
344 | ||
345 | static struct cramfs_inode *iget(unsigned int ino) | |
346 | { | |
347 | return cramfs_iget(romfs_read(ino)); | |
348 | } | |
349 | ||
63cccae4 KZ |
350 | static void iput(struct cramfs_inode *inode) |
351 | { | |
352 | free(inode); | |
353 | } | |
63cccae4 KZ |
354 | |
355 | /* | |
19922f22 | 356 | * Return the offset of the root directory |
63cccae4 KZ |
357 | */ |
358 | static struct cramfs_inode *read_super(void) | |
359 | { | |
fbaec83b SRP |
360 | struct cramfs_inode * root = cramfs_iget(&super.root); |
361 | unsigned long offset = root->offset << 2; | |
19922f22 | 362 | |
fbaec83b | 363 | if (!S_ISDIR(root->mode)) |
e56644ca | 364 | die(FSCK_UNCORRECTED, 0, _("root inode is not directory")); |
19922f22 KZ |
365 | if (!(super.flags & CRAMFS_FLAG_SHIFTED_ROOT_OFFSET) && |
366 | ((offset != sizeof(struct cramfs_super)) && | |
367 | (offset != PAD_SIZE + sizeof(struct cramfs_super)))) | |
368 | { | |
e56644ca | 369 | die(FSCK_UNCORRECTED, 0, _("bad root offset (%lu)"), offset); |
19922f22 | 370 | } |
fbaec83b | 371 | return root; |
63cccae4 KZ |
372 | } |
373 | ||
374 | static int uncompress_block(void *src, int len) | |
375 | { | |
376 | int err; | |
377 | ||
378 | stream.next_in = src; | |
379 | stream.avail_in = len; | |
380 | ||
381 | stream.next_out = (unsigned char *) outbuffer; | |
19922f22 | 382 | stream.avail_out = page_size*2; |
63cccae4 KZ |
383 | |
384 | inflateReset(&stream); | |
385 | ||
19922f22 | 386 | if (len > page_size*2) { |
e56644ca | 387 | die(FSCK_UNCORRECTED, 0, _("data block too large")); |
19922f22 | 388 | } |
63cccae4 KZ |
389 | err = inflate(&stream, Z_FINISH); |
390 | if (err != Z_STREAM_END) { | |
e56644ca | 391 | die(FSCK_UNCORRECTED, 0, _("decompression error %p(%d): %s"), |
19922f22 | 392 | zError(err), src, len); |
63cccae4 KZ |
393 | } |
394 | return stream.total_out; | |
395 | } | |
396 | ||
48d7b13a KZ |
397 | #if !HAVE_LCHOWN |
398 | #define lchown chown | |
63cccae4 | 399 | #endif |
19922f22 KZ |
400 | static void do_uncompress(char *path, int fd, unsigned long offset, unsigned long size) |
401 | { | |
402 | unsigned long curr = offset + 4 * ((size + page_size - 1) / page_size); | |
403 | ||
404 | do { | |
405 | unsigned long out = page_size; | |
fbaec83b | 406 | unsigned long next = u32_toggle_endianness(cramfs_is_big_endian, *(u32 *) romfs_read(offset)); |
19922f22 KZ |
407 | |
408 | if (next > end_data) { | |
409 | end_data = next; | |
410 | } | |
411 | ||
412 | offset += 4; | |
413 | if (curr == next) { | |
414 | if (opt_verbose > 1) { | |
fbaec83b | 415 | printf(_(" hole at %ld (%zd)\n"), curr, page_size); |
19922f22 KZ |
416 | } |
417 | if (size < page_size) | |
418 | out = size; | |
419 | memset(outbuffer, 0x00, out); | |
420 | } | |
421 | else { | |
422 | if (opt_verbose > 1) { | |
e56644ca | 423 | printf(_(" uncompressing block at %ld to %ld (%ld)\n"), curr, next, next - curr); |
19922f22 KZ |
424 | } |
425 | out = uncompress_block(romfs_read(curr), next - curr); | |
426 | } | |
427 | if (size >= page_size) { | |
428 | if (out != page_size) { | |
e56644ca | 429 | die(FSCK_UNCORRECTED, 0, _("non-block (%ld) bytes"), out); |
19922f22 KZ |
430 | } |
431 | } else { | |
432 | if (out != size) { | |
e56644ca | 433 | die(FSCK_UNCORRECTED, 0, _("non-size (%ld vs %ld) bytes"), out, size); |
19922f22 KZ |
434 | } |
435 | } | |
436 | size -= out; | |
437 | if (opt_extract) { | |
438 | if (write(fd, outbuffer, out) < 0) { | |
e56644ca | 439 | die(FSCK_ERROR, 1, _("write failed: %s"), path); |
19922f22 KZ |
440 | } |
441 | } | |
442 | curr = next; | |
443 | } while (size); | |
444 | } | |
63cccae4 KZ |
445 | |
446 | static void change_file_status(char *path, struct cramfs_inode *i) | |
447 | { | |
448 | struct utimbuf epoch = { 0, 0 }; | |
449 | ||
450 | if (euid == 0) { | |
48d7b13a | 451 | if (lchown(path, i->uid, i->gid) < 0) { |
e56644ca | 452 | die(FSCK_ERROR, 1, _("lchown failed: %s"), path); |
63cccae4 KZ |
453 | } |
454 | if (S_ISLNK(i->mode)) | |
455 | return; | |
456 | if ((S_ISUID | S_ISGID) & i->mode) { | |
457 | if (chmod(path, i->mode) < 0) { | |
e56644ca | 458 | die(FSCK_ERROR, 1, _("chown failed: %s"), path); |
63cccae4 KZ |
459 | } |
460 | } | |
461 | } | |
462 | if (S_ISLNK(i->mode)) | |
463 | return; | |
464 | if (utime(path, &epoch) < 0) { | |
e56644ca | 465 | die(FSCK_ERROR, 1, _("utime failed: %s"), path); |
19922f22 KZ |
466 | } |
467 | } | |
468 | ||
469 | static void do_directory(char *path, struct cramfs_inode *i) | |
470 | { | |
471 | int pathlen = strlen(path); | |
472 | int count = i->size; | |
473 | unsigned long offset = i->offset << 2; | |
474 | char *newpath = malloc(pathlen + 256); | |
475 | ||
476 | if (!newpath) { | |
e56644ca | 477 | die(FSCK_ERROR, 1, _("malloc failed")); |
19922f22 KZ |
478 | } |
479 | if (offset == 0 && count != 0) { | |
e56644ca | 480 | die(FSCK_UNCORRECTED, 0, _("directory inode has zero offset and non-zero size: %s"), path); |
19922f22 KZ |
481 | } |
482 | if (offset != 0 && offset < start_dir) { | |
483 | start_dir = offset; | |
484 | } | |
485 | /* TODO: Do we need to check end_dir for empty case? */ | |
486 | memcpy(newpath, path, pathlen); | |
487 | newpath[pathlen] = '/'; | |
488 | pathlen++; | |
489 | if (opt_verbose) { | |
490 | print_node('d', i, path); | |
491 | } | |
492 | if (opt_extract) { | |
493 | if (mkdir(path, i->mode) < 0) { | |
e56644ca | 494 | die(FSCK_ERROR, 1, _("mkdir failed: %s"), path); |
19922f22 KZ |
495 | } |
496 | change_file_status(path, i); | |
497 | } | |
498 | while (count > 0) { | |
499 | struct cramfs_inode *child = iget(offset); | |
500 | int size; | |
501 | int newlen = child->namelen << 2; | |
502 | ||
503 | size = sizeof(struct cramfs_inode) + newlen; | |
504 | count -= size; | |
505 | ||
506 | offset += sizeof(struct cramfs_inode); | |
507 | ||
508 | memcpy(newpath + pathlen, romfs_read(offset), newlen); | |
509 | newpath[pathlen + newlen] = 0; | |
510 | if (newlen == 0) { | |
e56644ca | 511 | die(FSCK_UNCORRECTED, 0, _("filename length is zero")); |
19922f22 KZ |
512 | } |
513 | if ((pathlen + newlen) - strlen(newpath) > 3) { | |
e56644ca | 514 | die(FSCK_UNCORRECTED, 0, _("bad filename length")); |
19922f22 KZ |
515 | } |
516 | expand_fs(newpath, child); | |
517 | ||
518 | offset += newlen; | |
519 | ||
520 | if (offset <= start_dir) { | |
e56644ca | 521 | die(FSCK_UNCORRECTED, 0, _("bad inode offset")); |
19922f22 KZ |
522 | } |
523 | if (offset > end_dir) { | |
524 | end_dir = offset; | |
525 | } | |
526 | iput(child); /* free(child) */ | |
527 | } | |
528 | free(newpath); | |
529 | } | |
530 | ||
531 | static void do_file(char *path, struct cramfs_inode *i) | |
532 | { | |
533 | unsigned long offset = i->offset << 2; | |
534 | int fd = 0; | |
535 | ||
536 | if (offset == 0 && i->size != 0) { | |
e56644ca | 537 | die(FSCK_UNCORRECTED, 0, _("file inode has zero offset and non-zero size")); |
19922f22 KZ |
538 | } |
539 | if (i->size == 0 && offset != 0) { | |
e56644ca | 540 | die(FSCK_UNCORRECTED, 0, _("file inode has zero size and non-zero offset")); |
19922f22 KZ |
541 | } |
542 | if (offset != 0 && offset < start_data) { | |
543 | start_data = offset; | |
544 | } | |
545 | if (opt_verbose) { | |
546 | print_node('f', i, path); | |
547 | } | |
548 | if (opt_extract) { | |
549 | fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, i->mode); | |
550 | if (fd < 0) { | |
e56644ca | 551 | die(FSCK_ERROR, 1, _("open failed: %s"), path); |
19922f22 KZ |
552 | } |
553 | } | |
554 | if (i->size) { | |
555 | do_uncompress(path, fd, offset, i->size); | |
556 | } | |
557 | if (opt_extract) { | |
558 | close(fd); | |
559 | change_file_status(path, i); | |
63cccae4 KZ |
560 | } |
561 | } | |
562 | ||
563 | static void do_symlink(char *path, struct cramfs_inode *i) | |
564 | { | |
565 | unsigned long offset = i->offset << 2; | |
566 | unsigned long curr = offset + 4; | |
fbaec83b | 567 | unsigned long next = u32_toggle_endianness(cramfs_is_big_endian, *(u32 *) romfs_read(offset)); |
63cccae4 KZ |
568 | unsigned long size; |
569 | ||
19922f22 | 570 | if (offset == 0) { |
e56644ca | 571 | die(FSCK_UNCORRECTED, 0, _("symbolic link has zero offset")); |
19922f22 KZ |
572 | } |
573 | if (i->size == 0) { | |
e56644ca | 574 | die(FSCK_UNCORRECTED, 0, _("symbolic link has zero size")); |
19922f22 KZ |
575 | } |
576 | ||
577 | if (offset < start_data) { | |
578 | start_data = offset; | |
579 | } | |
63cccae4 KZ |
580 | if (next > end_data) { |
581 | end_data = next; | |
582 | } | |
583 | ||
584 | size = uncompress_block(romfs_read(curr), next - curr); | |
585 | if (size != i->size) { | |
e56644ca | 586 | die(FSCK_UNCORRECTED, 0, _("size error in symlink: %s"), path); |
63cccae4 KZ |
587 | } |
588 | outbuffer[size] = 0; | |
589 | if (opt_verbose) { | |
590 | char *str; | |
591 | ||
19922f22 | 592 | asprintf(&str, "%s -> %s", path, outbuffer); |
63cccae4 KZ |
593 | print_node('l', i, str); |
594 | if (opt_verbose > 1) { | |
e56644ca | 595 | printf(_(" uncompressing block at %ld to %ld (%ld)\n"), curr, next, next - curr); |
63cccae4 | 596 | } |
19922f22 | 597 | free(str); |
63cccae4 KZ |
598 | } |
599 | if (opt_extract) { | |
19922f22 | 600 | if (symlink(outbuffer, path) < 0) { |
e56644ca | 601 | die(FSCK_ERROR, 1, _("symlink failed: %s"), path); |
19922f22 | 602 | } |
63cccae4 KZ |
603 | change_file_status(path, i); |
604 | } | |
605 | } | |
606 | ||
607 | static void do_special_inode(char *path, struct cramfs_inode *i) | |
608 | { | |
609 | dev_t devtype = 0; | |
610 | char type; | |
611 | ||
19922f22 | 612 | if (i->offset) { /* no need to shift offset */ |
e56644ca | 613 | die(FSCK_UNCORRECTED, 0, _("special file has non-zero offset: %s"), path); |
19922f22 | 614 | } |
63cccae4 KZ |
615 | if (S_ISCHR(i->mode)) { |
616 | devtype = i->size; | |
617 | type = 'c'; | |
618 | } | |
619 | else if (S_ISBLK(i->mode)) { | |
620 | devtype = i->size; | |
621 | type = 'b'; | |
622 | } | |
19922f22 KZ |
623 | else if (S_ISFIFO(i->mode)) { |
624 | if (i->size != 0) { | |
e56644ca | 625 | die(FSCK_UNCORRECTED, 0, _("fifo has non-zero size: %s"), path); |
19922f22 | 626 | } |
63cccae4 | 627 | type = 'p'; |
19922f22 KZ |
628 | } |
629 | else if (S_ISSOCK(i->mode)) { | |
630 | if (i->size != 0) { | |
e56644ca | 631 | die(FSCK_UNCORRECTED, 0, _("socket has non-zero size: %s"), path); |
19922f22 | 632 | } |
63cccae4 | 633 | type = 's'; |
19922f22 | 634 | } |
63cccae4 | 635 | else { |
e56644ca | 636 | die(FSCK_UNCORRECTED, 0, _("bogus mode: %s (%o)"), path, i->mode); |
19922f22 | 637 | return; /* not reached */ |
63cccae4 KZ |
638 | } |
639 | ||
640 | if (opt_verbose) { | |
641 | print_node(type, i, path); | |
642 | } | |
643 | ||
644 | if (opt_extract) { | |
645 | if (mknod(path, i->mode, devtype) < 0) { | |
e56644ca | 646 | die(FSCK_ERROR, 1, _("mknod failed: %s"), path); |
63cccae4 KZ |
647 | } |
648 | change_file_status(path, i); | |
649 | } | |
650 | } | |
651 | ||
19922f22 | 652 | static void expand_fs(char *path, struct cramfs_inode *inode) |
63cccae4 | 653 | { |
19922f22 KZ |
654 | if (S_ISDIR(inode->mode)) { |
655 | do_directory(path, inode); | |
656 | } | |
657 | else if (S_ISREG(inode->mode)) { | |
658 | do_file(path, inode); | |
659 | } | |
660 | else if (S_ISLNK(inode->mode)) { | |
661 | do_symlink(path, inode); | |
662 | } | |
663 | else { | |
664 | do_special_inode(path, inode); | |
665 | } | |
63cccae4 KZ |
666 | } |
667 | ||
19922f22 | 668 | static void test_fs(int start) |
63cccae4 | 669 | { |
19922f22 | 670 | struct cramfs_inode *root; |
63cccae4 | 671 | |
19922f22 KZ |
672 | root = read_super(); |
673 | umask(0); | |
674 | euid = geteuid(); | |
675 | stream.next_in = NULL; | |
676 | stream.avail_in = 0; | |
677 | inflateInit(&stream); | |
678 | expand_fs(extract_dir, root); | |
679 | inflateEnd(&stream); | |
680 | if (start_data != ~0UL) { | |
681 | if (start_data < (sizeof(struct cramfs_super) + start)) { | |
e56644ca | 682 | die(FSCK_UNCORRECTED, 0, _("directory data start (%ld) < sizeof(struct cramfs_super) + start (%ld)"), start_data, sizeof(struct cramfs_super) + start); |
63cccae4 | 683 | } |
19922f22 | 684 | if (end_dir != start_data) { |
e56644ca | 685 | die(FSCK_UNCORRECTED, 0, _("directory data end (%ld) != file data start (%ld)"), end_dir, start_data); |
63cccae4 | 686 | } |
63cccae4 | 687 | } |
19922f22 KZ |
688 | if (super.flags & CRAMFS_FLAG_FSID_VERSION_2) { |
689 | if (end_data > super.size) { | |
e56644ca | 690 | die(FSCK_UNCORRECTED, 0, _("invalid file data offset")); |
63cccae4 | 691 | } |
63cccae4 | 692 | } |
19922f22 | 693 | iput(root); /* free(root) */ |
63cccae4 KZ |
694 | } |
695 | #endif /* INCLUDE_FS_TESTS */ | |
696 | ||
697 | int main(int argc, char **argv) | |
698 | { | |
63cccae4 KZ |
699 | int c; /* for getopt */ |
700 | int start = 0; | |
293889d0 | 701 | size_t length = 0; |
19922f22 | 702 | |
e56644ca PR |
703 | setlocale(LC_MESSAGES, ""); |
704 | bindtextdomain(PACKAGE, LOCALEDIR); | |
705 | textdomain(PACKAGE); | |
706 | ||
d144727b | 707 | page_size = getpagesize(); |
63cccae4 KZ |
708 | |
709 | if (argc) | |
710 | progname = argv[0]; | |
711 | ||
19922f22 KZ |
712 | outbuffer = malloc(page_size * 2); |
713 | if (!outbuffer) | |
e56644ca | 714 | die(FSCK_ERROR, 1, _("failed to allocate outbuffer")); |
19922f22 | 715 | |
63cccae4 KZ |
716 | /* command line options */ |
717 | while ((c = getopt(argc, argv, "hx:v")) != EOF) { | |
718 | switch (c) { | |
719 | case 'h': | |
19922f22 | 720 | usage(FSCK_OK); |
63cccae4 KZ |
721 | case 'x': |
722 | #ifdef INCLUDE_FS_TESTS | |
723 | opt_extract = 1; | |
19922f22 | 724 | extract_dir = optarg; |
63cccae4 | 725 | break; |
19922f22 | 726 | #else /* not INCLUDE_FS_TESTS */ |
e56644ca | 727 | die(FSCK_USAGE, 0, _("compiled without -x support")); |
63cccae4 KZ |
728 | #endif /* not INCLUDE_FS_TESTS */ |
729 | case 'v': | |
730 | opt_verbose++; | |
731 | break; | |
732 | } | |
733 | } | |
734 | ||
735 | if ((argc - optind) != 1) | |
19922f22 | 736 | usage(FSCK_USAGE); |
63cccae4 KZ |
737 | filename = argv[optind]; |
738 | ||
19922f22 KZ |
739 | test_super(&start, &length); |
740 | test_crc(start); | |
63cccae4 | 741 | #ifdef INCLUDE_FS_TESTS |
19922f22 KZ |
742 | test_fs(start); |
743 | #endif /* INCLUDE_FS_TESTS */ | |
63cccae4 | 744 | |
19922f22 KZ |
745 | if (opt_verbose) { |
746 | printf("%s: OK\n", filename); | |
63cccae4 | 747 | } |
63cccae4 | 748 | |
19922f22 | 749 | exit(FSCK_OK); |
63cccae4 | 750 | } |