1#include <stdio.h>
2#include <stdlib.h>
3#include <sys/types.h>
4#include <sys/stat.h>
5#include <string.h>
6#include <unistd.h>
7#include <time.h>
8#include <fcntl.h>
9#include <errno.h>
10#include <ctype.h>
11#include <limits.h>
12
13/*
14 * Original work by Jeff Garzik
15 *
16 * External file lists, symlink, pipe and fifo support by Thayne Harbaugh
17 * Hard link support by Luciano Rocha
18 */
19
20#define xstr(s) #s
21#define str(s) xstr(s)
22
23static unsigned int offset;
24static unsigned int ino = 721;
25static time_t default_mtime;
26
27struct file_handler {
28	const char *type;
29	int (*handler)(const char *line);
30};
31
32static void push_string(const char *name)
33{
34	unsigned int name_len = strlen(name) + 1;
35
36	fputs(name, stdout);
37	putchar(0);
38	offset += name_len;
39}
40
41static void push_pad (void)
42{
43	while (offset & 3) {
44		putchar(0);
45		offset++;
46	}
47}
48
49static void push_rest(const char *name)
50{
51	unsigned int name_len = strlen(name) + 1;
52	unsigned int tmp_ofs;
53
54	fputs(name, stdout);
55	putchar(0);
56	offset += name_len;
57
58	tmp_ofs = name_len + 110;
59	while (tmp_ofs & 3) {
60		putchar(0);
61		offset++;
62		tmp_ofs++;
63	}
64}
65
66static void push_hdr(const char *s)
67{
68	fputs(s, stdout);
69	offset += 110;
70}
71
72static void cpio_trailer(void)
73{
74	char s[256];
75	const char name[] = "TRAILER!!!";
76
77	sprintf(s, "%s%08X%08X%08lX%08lX%08X%08lX"
78	       "%08X%08X%08X%08X%08X%08X%08X",
79		"070701",		/* magic */
80		0,			/* ino */
81		0,			/* mode */
82		(long) 0,		/* uid */
83		(long) 0,		/* gid */
84		1,			/* nlink */
85		(long) 0,		/* mtime */
86		0,			/* filesize */
87		0,			/* major */
88		0,			/* minor */
89		0,			/* rmajor */
90		0,			/* rminor */
91		(unsigned)strlen(name)+1, /* namesize */
92		0);			/* chksum */
93	push_hdr(s);
94	push_rest(name);
95
96	while (offset % 512) {
97		putchar(0);
98		offset++;
99	}
100}
101
102static int cpio_mkslink(const char *name, const char *target,
103			 unsigned int mode, uid_t uid, gid_t gid)
104{
105	char s[256];
106
107	if (name[0] == '/')
108		name++;
109	sprintf(s,"%s%08X%08X%08lX%08lX%08X%08lX"
110	       "%08X%08X%08X%08X%08X%08X%08X",
111		"070701",		/* magic */
112		ino++,			/* ino */
113		S_IFLNK | mode,		/* mode */
114		(long) uid,		/* uid */
115		(long) gid,		/* gid */
116		1,			/* nlink */
117		(long) default_mtime,	/* mtime */
118		(unsigned)strlen(target)+1, /* filesize */
119		3,			/* major */
120		1,			/* minor */
121		0,			/* rmajor */
122		0,			/* rminor */
123		(unsigned)strlen(name) + 1,/* namesize */
124		0);			/* chksum */
125	push_hdr(s);
126	push_string(name);
127	push_pad();
128	push_string(target);
129	push_pad();
130	return 0;
131}
132
133static int cpio_mkslink_line(const char *line)
134{
135	char name[PATH_MAX + 1];
136	char target[PATH_MAX + 1];
137	unsigned int mode;
138	int uid;
139	int gid;
140	int rc = -1;
141
142	if (5 != sscanf(line, "%" str(PATH_MAX) "s %" str(PATH_MAX) "s %o %d %d", name, target, &mode, &uid, &gid)) {
143		fprintf(stderr, "Unrecognized dir format '%s'", line);
144		goto fail;
145	}
146	rc = cpio_mkslink(name, target, mode, uid, gid);
147 fail:
148	return rc;
149}
150
151static int cpio_mkgeneric(const char *name, unsigned int mode,
152		       uid_t uid, gid_t gid)
153{
154	char s[256];
155
156	if (name[0] == '/')
157		name++;
158	sprintf(s,"%s%08X%08X%08lX%08lX%08X%08lX"
159	       "%08X%08X%08X%08X%08X%08X%08X",
160		"070701",		/* magic */
161		ino++,			/* ino */
162		mode,			/* mode */
163		(long) uid,		/* uid */
164		(long) gid,		/* gid */
165		2,			/* nlink */
166		(long) default_mtime,	/* mtime */
167		0,			/* filesize */
168		3,			/* major */
169		1,			/* minor */
170		0,			/* rmajor */
171		0,			/* rminor */
172		(unsigned)strlen(name) + 1,/* namesize */
173		0);			/* chksum */
174	push_hdr(s);
175	push_rest(name);
176	return 0;
177}
178
179enum generic_types {
180	GT_DIR,
181	GT_PIPE,
182	GT_SOCK
183};
184
185struct generic_type {
186	const char *type;
187	mode_t mode;
188};
189
190static struct generic_type generic_type_table[] = {
191	[GT_DIR] = {
192		.type = "dir",
193		.mode = S_IFDIR
194	},
195	[GT_PIPE] = {
196		.type = "pipe",
197		.mode = S_IFIFO
198	},
199	[GT_SOCK] = {
200		.type = "sock",
201		.mode = S_IFSOCK
202	}
203};
204
205static int cpio_mkgeneric_line(const char *line, enum generic_types gt)
206{
207	char name[PATH_MAX + 1];
208	unsigned int mode;
209	int uid;
210	int gid;
211	int rc = -1;
212
213	if (4 != sscanf(line, "%" str(PATH_MAX) "s %o %d %d", name, &mode, &uid, &gid)) {
214		fprintf(stderr, "Unrecognized %s format '%s'",
215			line, generic_type_table[gt].type);
216		goto fail;
217	}
218	mode |= generic_type_table[gt].mode;
219	rc = cpio_mkgeneric(name, mode, uid, gid);
220 fail:
221	return rc;
222}
223
224static int cpio_mkdir_line(const char *line)
225{
226	return cpio_mkgeneric_line(line, GT_DIR);
227}
228
229static int cpio_mkpipe_line(const char *line)
230{
231	return cpio_mkgeneric_line(line, GT_PIPE);
232}
233
234static int cpio_mksock_line(const char *line)
235{
236	return cpio_mkgeneric_line(line, GT_SOCK);
237}
238
239static int cpio_mknod(const char *name, unsigned int mode,
240		       uid_t uid, gid_t gid, char dev_type,
241		       unsigned int maj, unsigned int min)
242{
243	char s[256];
244
245	if (dev_type == 'b')
246		mode |= S_IFBLK;
247	else
248		mode |= S_IFCHR;
249
250	if (name[0] == '/')
251		name++;
252	sprintf(s,"%s%08X%08X%08lX%08lX%08X%08lX"
253	       "%08X%08X%08X%08X%08X%08X%08X",
254		"070701",		/* magic */
255		ino++,			/* ino */
256		mode,			/* mode */
257		(long) uid,		/* uid */
258		(long) gid,		/* gid */
259		1,			/* nlink */
260		(long) default_mtime,	/* mtime */
261		0,			/* filesize */
262		3,			/* major */
263		1,			/* minor */
264		maj,			/* rmajor */
265		min,			/* rminor */
266		(unsigned)strlen(name) + 1,/* namesize */
267		0);			/* chksum */
268	push_hdr(s);
269	push_rest(name);
270	return 0;
271}
272
273static int cpio_mknod_line(const char *line)
274{
275	char name[PATH_MAX + 1];
276	unsigned int mode;
277	int uid;
278	int gid;
279	char dev_type;
280	unsigned int maj;
281	unsigned int min;
282	int rc = -1;
283
284	if (7 != sscanf(line, "%" str(PATH_MAX) "s %o %d %d %c %u %u",
285			 name, &mode, &uid, &gid, &dev_type, &maj, &min)) {
286		fprintf(stderr, "Unrecognized nod format '%s'", line);
287		goto fail;
288	}
289	rc = cpio_mknod(name, mode, uid, gid, dev_type, maj, min);
290 fail:
291	return rc;
292}
293
294static int cpio_mkfile(const char *name, const char *location,
295			unsigned int mode, uid_t uid, gid_t gid,
296			unsigned int nlinks)
297{
298	char s[256];
299	char *filebuf = NULL;
300	struct stat buf;
301	long size;
302	int file = -1;
303	int retval;
304	int rc = -1;
305	int namesize;
306	unsigned int i;
307
308	mode |= S_IFREG;
309
310	file = open (location, O_RDONLY);
311	if (file < 0) {
312		fprintf (stderr, "File %s could not be opened for reading\n", location);
313		goto error;
314	}
315
316	retval = fstat(file, &buf);
317	if (retval) {
318		fprintf(stderr, "File %s could not be stat()'ed\n", location);
319		goto error;
320	}
321
322	filebuf = malloc(buf.st_size);
323	if (!filebuf) {
324		fprintf (stderr, "out of memory\n");
325		goto error;
326	}
327
328	retval = read (file, filebuf, buf.st_size);
329	if (retval < 0) {
330		fprintf (stderr, "Can not read %s file\n", location);
331		goto error;
332	}
333
334	size = 0;
335	for (i = 1; i <= nlinks; i++) {
336		/* data goes on last link */
337		if (i == nlinks) size = buf.st_size;
338
339		if (name[0] == '/')
340			name++;
341		namesize = strlen(name) + 1;
342		sprintf(s,"%s%08X%08X%08lX%08lX%08X%08lX"
343		       "%08lX%08X%08X%08X%08X%08X%08X",
344			"070701",		/* magic */
345			ino,			/* ino */
346			mode,			/* mode */
347			(long) uid,		/* uid */
348			(long) gid,		/* gid */
349			nlinks,			/* nlink */
350			(long) buf.st_mtime,	/* mtime */
351			size,			/* filesize */
352			3,			/* major */
353			1,			/* minor */
354			0,			/* rmajor */
355			0,			/* rminor */
356			namesize,		/* namesize */
357			0);			/* chksum */
358		push_hdr(s);
359		push_string(name);
360		push_pad();
361
362		if (size) {
363			if (fwrite(filebuf, size, 1, stdout) != 1) {
364				fprintf(stderr, "writing filebuf failed\n");
365				goto error;
366			}
367			offset += size;
368			push_pad();
369		}
370
371		name += namesize;
372	}
373	ino++;
374	rc = 0;
375
376error:
377	if (filebuf) free(filebuf);
378	if (file >= 0) close(file);
379	return rc;
380}
381
382static char *cpio_replace_env(char *new_location)
383{
384	char expanded[PATH_MAX + 1];
385	char *start, *end, *var;
386
387	while ((start = strstr(new_location, "${")) &&
388	       (end = strchr(start + 2, '}'))) {
389		*start = *end = 0;
390		var = getenv(start + 2);
391		snprintf(expanded, sizeof expanded, "%s%s%s",
392			 new_location, var ? var : "", end + 1);
393		strcpy(new_location, expanded);
394	}
395
396	return new_location;
397}
398
399static int cpio_mkfile_line(const char *line)
400{
401	char name[PATH_MAX + 1];
402	char *dname = NULL; /* malloc'ed buffer for hard links */
403	char location[PATH_MAX + 1];
404	unsigned int mode;
405	int uid;
406	int gid;
407	int nlinks = 1;
408	int end = 0, dname_len = 0;
409	int rc = -1;
410
411	if (5 > sscanf(line, "%" str(PATH_MAX) "s %" str(PATH_MAX)
412				"s %o %d %d %n",
413				name, location, &mode, &uid, &gid, &end)) {
414		fprintf(stderr, "Unrecognized file format '%s'", line);
415		goto fail;
416	}
417	if (end && isgraph(line[end])) {
418		int len;
419		int nend;
420
421		dname = malloc(strlen(line));
422		if (!dname) {
423			fprintf (stderr, "out of memory (%d)\n", dname_len);
424			goto fail;
425		}
426
427		dname_len = strlen(name) + 1;
428		memcpy(dname, name, dname_len);
429
430		do {
431			nend = 0;
432			if (sscanf(line + end, "%" str(PATH_MAX) "s %n",
433					name, &nend) < 1)
434				break;
435			len = strlen(name) + 1;
436			memcpy(dname + dname_len, name, len);
437			dname_len += len;
438			nlinks++;
439			end += nend;
440		} while (isgraph(line[end]));
441	} else {
442		dname = name;
443	}
444	rc = cpio_mkfile(dname, cpio_replace_env(location),
445	                 mode, uid, gid, nlinks);
446 fail:
447	if (dname_len) free(dname);
448	return rc;
449}
450
451static void usage(const char *prog)
452{
453	fprintf(stderr, "Usage:\n"
454		"\t%s [-t <timestamp>] <cpio_list>\n"
455		"\n"
456		"<cpio_list> is a file containing newline separated entries that\n"
457		"describe the files to be included in the initramfs archive:\n"
458		"\n"
459		"# a comment\n"
460		"file <name> <location> <mode> <uid> <gid> [<hard links>]\n"
461		"dir <name> <mode> <uid> <gid>\n"
462		"nod <name> <mode> <uid> <gid> <dev_type> <maj> <min>\n"
463		"slink <name> <target> <mode> <uid> <gid>\n"
464		"pipe <name> <mode> <uid> <gid>\n"
465		"sock <name> <mode> <uid> <gid>\n"
466		"\n"
467		"<name>       name of the file/dir/nod/etc in the archive\n"
468		"<location>   location of the file in the current filesystem\n"
469		"             expands shell variables quoted with ${}\n"
470		"<target>     link target\n"
471		"<mode>       mode/permissions of the file\n"
472		"<uid>        user id (0=root)\n"
473		"<gid>        group id (0=root)\n"
474		"<dev_type>   device type (b=block, c=character)\n"
475		"<maj>        major number of nod\n"
476		"<min>        minor number of nod\n"
477		"<hard links> space separated list of other links to file\n"
478		"\n"
479		"example:\n"
480		"# A simple initramfs\n"
481		"dir /dev 0755 0 0\n"
482		"nod /dev/console 0600 0 0 c 5 1\n"
483		"dir /root 0700 0 0\n"
484		"dir /sbin 0755 0 0\n"
485		"file /sbin/kinit /usr/src/klibc/kinit/kinit 0755 0 0\n"
486		"\n"
487		"<timestamp> is time in seconds since Epoch that will be used\n"
488		"as mtime for symlinks, special files and directories. The default\n"
489		"is to use the current time for these entries.\n",
490		prog);
491}
492
493struct file_handler file_handler_table[] = {
494	{
495		.type    = "file",
496		.handler = cpio_mkfile_line,
497	}, {
498		.type    = "nod",
499		.handler = cpio_mknod_line,
500	}, {
501		.type    = "dir",
502		.handler = cpio_mkdir_line,
503	}, {
504		.type    = "slink",
505		.handler = cpio_mkslink_line,
506	}, {
507		.type    = "pipe",
508		.handler = cpio_mkpipe_line,
509	}, {
510		.type    = "sock",
511		.handler = cpio_mksock_line,
512	}, {
513		.type    = NULL,
514		.handler = NULL,
515	}
516};
517
518#define LINE_SIZE (2 * PATH_MAX + 50)
519
520int main (int argc, char *argv[])
521{
522	FILE *cpio_list;
523	char line[LINE_SIZE];
524	char *args, *type;
525	int ec = 0;
526	int line_nr = 0;
527	const char *filename;
528
529	default_mtime = time(NULL);
530	while (1) {
531		int opt = getopt(argc, argv, "t:h");
532		char *invalid;
533
534		if (opt == -1)
535			break;
536		switch (opt) {
537		case 't':
538			default_mtime = strtol(optarg, &invalid, 10);
539			if (!*optarg || *invalid) {
540				fprintf(stderr, "Invalid timestamp: %s\n",
541						optarg);
542				usage(argv[0]);
543				exit(1);
544			}
545			break;
546		case 'h':
547		case '?':
548			usage(argv[0]);
549			exit(opt == 'h' ? 0 : 1);
550		}
551	}
552
553	if (argc - optind != 1) {
554		usage(argv[0]);
555		exit(1);
556	}
557	filename = argv[optind];
558	if (!strcmp(filename, "-"))
559		cpio_list = stdin;
560	else if (!(cpio_list = fopen(filename, "r"))) {
561		fprintf(stderr, "ERROR: unable to open '%s': %s\n\n",
562			filename, strerror(errno));
563		usage(argv[0]);
564		exit(1);
565	}
566
567	while (fgets(line, LINE_SIZE, cpio_list)) {
568		int type_idx;
569		size_t slen = strlen(line);
570
571		line_nr++;
572
573		if ('#' == *line) {
574			/* comment - skip to next line */
575			continue;
576		}
577
578		if (! (type = strtok(line, " \t"))) {
579			fprintf(stderr,
580				"ERROR: incorrect format, could not locate file type line %d: '%s'\n",
581				line_nr, line);
582			ec = -1;
583			break;
584		}
585
586		if ('\n' == *type) {
587			/* a blank line */
588			continue;
589		}
590
591		if (slen == strlen(type)) {
592			/* must be an empty line */
593			continue;
594		}
595
596		if (! (args = strtok(NULL, "\n"))) {
597			fprintf(stderr,
598				"ERROR: incorrect format, newline required line %d: '%s'\n",
599				line_nr, line);
600			ec = -1;
601		}
602
603		for (type_idx = 0; file_handler_table[type_idx].type; type_idx++) {
604			int rc;
605			if (! strcmp(line, file_handler_table[type_idx].type)) {
606				if ((rc = file_handler_table[type_idx].handler(args))) {
607					ec = rc;
608					fprintf(stderr, " line %d\n", line_nr);
609				}
610				break;
611			}
612		}
613
614		if (NULL == file_handler_table[type_idx].type) {
615			fprintf(stderr, "unknown file type line %d: '%s'\n",
616				line_nr, line);
617		}
618	}
619	if (ec == 0)
620		cpio_trailer();
621
622	exit(ec);
623}
624