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