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