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