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