xref: /linux/usr/gen_init_cpio.c (revision f79e4d5f92a129a1159c973735007d4ddc8541f3)
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 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 	filebuf = malloc(buf.st_size);
324 	if (!filebuf) {
325 		fprintf (stderr, "out of memory\n");
326 		goto error;
327 	}
328 
329 	retval = read (file, filebuf, buf.st_size);
330 	if (retval < 0) {
331 		fprintf (stderr, "Can not read %s file\n", location);
332 		goto error;
333 	}
334 
335 	size = 0;
336 	for (i = 1; i <= nlinks; i++) {
337 		/* data goes on last link */
338 		if (i == nlinks) size = buf.st_size;
339 
340 		if (name[0] == '/')
341 			name++;
342 		namesize = strlen(name) + 1;
343 		sprintf(s,"%s%08X%08X%08lX%08lX%08X%08lX"
344 		       "%08lX%08X%08X%08X%08X%08X%08X",
345 			"070701",		/* magic */
346 			ino,			/* ino */
347 			mode,			/* mode */
348 			(long) uid,		/* uid */
349 			(long) gid,		/* gid */
350 			nlinks,			/* nlink */
351 			(long) buf.st_mtime,	/* mtime */
352 			size,			/* filesize */
353 			3,			/* major */
354 			1,			/* minor */
355 			0,			/* rmajor */
356 			0,			/* rminor */
357 			namesize,		/* namesize */
358 			0);			/* chksum */
359 		push_hdr(s);
360 		push_string(name);
361 		push_pad();
362 
363 		if (size) {
364 			if (fwrite(filebuf, size, 1, stdout) != 1) {
365 				fprintf(stderr, "writing filebuf failed\n");
366 				goto error;
367 			}
368 			offset += size;
369 			push_pad();
370 		}
371 
372 		name += namesize;
373 	}
374 	ino++;
375 	rc = 0;
376 
377 error:
378 	if (filebuf) free(filebuf);
379 	if (file >= 0) close(file);
380 	return rc;
381 }
382 
383 static char *cpio_replace_env(char *new_location)
384 {
385 	char expanded[PATH_MAX + 1];
386 	char *start, *end, *var;
387 
388 	while ((start = strstr(new_location, "${")) &&
389 	       (end = strchr(start + 2, '}'))) {
390 		*start = *end = 0;
391 		var = getenv(start + 2);
392 		snprintf(expanded, sizeof expanded, "%s%s%s",
393 			 new_location, var ? var : "", end + 1);
394 		strcpy(new_location, expanded);
395 	}
396 
397 	return new_location;
398 }
399 
400 static int cpio_mkfile_line(const char *line)
401 {
402 	char name[PATH_MAX + 1];
403 	char *dname = NULL; /* malloc'ed buffer for hard links */
404 	char location[PATH_MAX + 1];
405 	unsigned int mode;
406 	int uid;
407 	int gid;
408 	int nlinks = 1;
409 	int end = 0, dname_len = 0;
410 	int rc = -1;
411 
412 	if (5 > sscanf(line, "%" str(PATH_MAX) "s %" str(PATH_MAX)
413 				"s %o %d %d %n",
414 				name, location, &mode, &uid, &gid, &end)) {
415 		fprintf(stderr, "Unrecognized file format '%s'", line);
416 		goto fail;
417 	}
418 	if (end && isgraph(line[end])) {
419 		int len;
420 		int nend;
421 
422 		dname = malloc(strlen(line));
423 		if (!dname) {
424 			fprintf (stderr, "out of memory (%d)\n", dname_len);
425 			goto fail;
426 		}
427 
428 		dname_len = strlen(name) + 1;
429 		memcpy(dname, name, dname_len);
430 
431 		do {
432 			nend = 0;
433 			if (sscanf(line + end, "%" str(PATH_MAX) "s %n",
434 					name, &nend) < 1)
435 				break;
436 			len = strlen(name) + 1;
437 			memcpy(dname + dname_len, name, len);
438 			dname_len += len;
439 			nlinks++;
440 			end += nend;
441 		} while (isgraph(line[end]));
442 	} else {
443 		dname = name;
444 	}
445 	rc = cpio_mkfile(dname, cpio_replace_env(location),
446 	                 mode, uid, gid, nlinks);
447  fail:
448 	if (dname_len) free(dname);
449 	return rc;
450 }
451 
452 static void usage(const char *prog)
453 {
454 	fprintf(stderr, "Usage:\n"
455 		"\t%s [-t <timestamp>] <cpio_list>\n"
456 		"\n"
457 		"<cpio_list> is a file containing newline separated entries that\n"
458 		"describe the files to be included in the initramfs archive:\n"
459 		"\n"
460 		"# a comment\n"
461 		"file <name> <location> <mode> <uid> <gid> [<hard links>]\n"
462 		"dir <name> <mode> <uid> <gid>\n"
463 		"nod <name> <mode> <uid> <gid> <dev_type> <maj> <min>\n"
464 		"slink <name> <target> <mode> <uid> <gid>\n"
465 		"pipe <name> <mode> <uid> <gid>\n"
466 		"sock <name> <mode> <uid> <gid>\n"
467 		"\n"
468 		"<name>       name of the file/dir/nod/etc in the archive\n"
469 		"<location>   location of the file in the current filesystem\n"
470 		"             expands shell variables quoted with ${}\n"
471 		"<target>     link target\n"
472 		"<mode>       mode/permissions of the file\n"
473 		"<uid>        user id (0=root)\n"
474 		"<gid>        group id (0=root)\n"
475 		"<dev_type>   device type (b=block, c=character)\n"
476 		"<maj>        major number of nod\n"
477 		"<min>        minor number of nod\n"
478 		"<hard links> space separated list of other links to file\n"
479 		"\n"
480 		"example:\n"
481 		"# A simple initramfs\n"
482 		"dir /dev 0755 0 0\n"
483 		"nod /dev/console 0600 0 0 c 5 1\n"
484 		"dir /root 0700 0 0\n"
485 		"dir /sbin 0755 0 0\n"
486 		"file /sbin/kinit /usr/src/klibc/kinit/kinit 0755 0 0\n"
487 		"\n"
488 		"<timestamp> is time in seconds since Epoch that will be used\n"
489 		"as mtime for symlinks, special files and directories. The default\n"
490 		"is to use the current time for these entries.\n",
491 		prog);
492 }
493 
494 struct file_handler file_handler_table[] = {
495 	{
496 		.type    = "file",
497 		.handler = cpio_mkfile_line,
498 	}, {
499 		.type    = "nod",
500 		.handler = cpio_mknod_line,
501 	}, {
502 		.type    = "dir",
503 		.handler = cpio_mkdir_line,
504 	}, {
505 		.type    = "slink",
506 		.handler = cpio_mkslink_line,
507 	}, {
508 		.type    = "pipe",
509 		.handler = cpio_mkpipe_line,
510 	}, {
511 		.type    = "sock",
512 		.handler = cpio_mksock_line,
513 	}, {
514 		.type    = NULL,
515 		.handler = NULL,
516 	}
517 };
518 
519 #define LINE_SIZE (2 * PATH_MAX + 50)
520 
521 int main (int argc, char *argv[])
522 {
523 	FILE *cpio_list;
524 	char line[LINE_SIZE];
525 	char *args, *type;
526 	int ec = 0;
527 	int line_nr = 0;
528 	const char *filename;
529 
530 	default_mtime = time(NULL);
531 	while (1) {
532 		int opt = getopt(argc, argv, "t:h");
533 		char *invalid;
534 
535 		if (opt == -1)
536 			break;
537 		switch (opt) {
538 		case 't':
539 			default_mtime = strtol(optarg, &invalid, 10);
540 			if (!*optarg || *invalid) {
541 				fprintf(stderr, "Invalid timestamp: %s\n",
542 						optarg);
543 				usage(argv[0]);
544 				exit(1);
545 			}
546 			break;
547 		case 'h':
548 		case '?':
549 			usage(argv[0]);
550 			exit(opt == 'h' ? 0 : 1);
551 		}
552 	}
553 
554 	if (argc - optind != 1) {
555 		usage(argv[0]);
556 		exit(1);
557 	}
558 	filename = argv[optind];
559 	if (!strcmp(filename, "-"))
560 		cpio_list = stdin;
561 	else if (!(cpio_list = fopen(filename, "r"))) {
562 		fprintf(stderr, "ERROR: unable to open '%s': %s\n\n",
563 			filename, strerror(errno));
564 		usage(argv[0]);
565 		exit(1);
566 	}
567 
568 	while (fgets(line, LINE_SIZE, cpio_list)) {
569 		int type_idx;
570 		size_t slen = strlen(line);
571 
572 		line_nr++;
573 
574 		if ('#' == *line) {
575 			/* comment - skip to next line */
576 			continue;
577 		}
578 
579 		if (! (type = strtok(line, " \t"))) {
580 			fprintf(stderr,
581 				"ERROR: incorrect format, could not locate file type line %d: '%s'\n",
582 				line_nr, line);
583 			ec = -1;
584 			break;
585 		}
586 
587 		if ('\n' == *type) {
588 			/* a blank line */
589 			continue;
590 		}
591 
592 		if (slen == strlen(type)) {
593 			/* must be an empty line */
594 			continue;
595 		}
596 
597 		if (! (args = strtok(NULL, "\n"))) {
598 			fprintf(stderr,
599 				"ERROR: incorrect format, newline required line %d: '%s'\n",
600 				line_nr, line);
601 			ec = -1;
602 		}
603 
604 		for (type_idx = 0; file_handler_table[type_idx].type; type_idx++) {
605 			int rc;
606 			if (! strcmp(line, file_handler_table[type_idx].type)) {
607 				if ((rc = file_handler_table[type_idx].handler(args))) {
608 					ec = rc;
609 					fprintf(stderr, " line %d\n", line_nr);
610 				}
611 				break;
612 			}
613 		}
614 
615 		if (NULL == file_handler_table[type_idx].type) {
616 			fprintf(stderr, "unknown file type line %d: '%s'\n",
617 				line_nr, line);
618 		}
619 	}
620 	if (ec == 0)
621 		cpio_trailer();
622 
623 	exit(ec);
624 }
625