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