gen_init_cpio: fix short read file handling
[linux-2.6-microblaze.git] / usr / gen_init_cpio.c
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 #define MIN(a, b) ((a) < (b) ? (a) : (b))
24
25 static unsigned int offset;
26 static unsigned int ino = 721;
27 static time_t default_mtime;
28
29 struct file_handler {
30         const char *type;
31         int (*handler)(const char *line);
32 };
33
34 static 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
43 static void push_pad (void)
44 {
45         while (offset & 3) {
46                 putchar(0);
47                 offset++;
48         }
49 }
50
51 static 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
68 static void push_hdr(const char *s)
69 {
70         fputs(s, stdout);
71         offset += 110;
72 }
73
74 static 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
104 static int cpio_mkslink(const char *name, const char *target,
105                          unsigned int mode, uid_t uid, gid_t gid)
106 {
107         char s[256];
108
109         if (name[0] == '/')
110                 name++;
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 */
119                 (long) default_mtime,   /* mtime */
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
135 static 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
153 static int cpio_mkgeneric(const char *name, unsigned int mode,
154                        uid_t uid, gid_t gid)
155 {
156         char s[256];
157
158         if (name[0] == '/')
159                 name++;
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 */
168                 (long) default_mtime,   /* mtime */
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
181 enum generic_types {
182         GT_DIR,
183         GT_PIPE,
184         GT_SOCK
185 };
186
187 struct generic_type {
188         const char *type;
189         mode_t mode;
190 };
191
192 static const struct generic_type generic_type_table[] = {
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
207 static 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
226 static int cpio_mkdir_line(const char *line)
227 {
228         return cpio_mkgeneric_line(line, GT_DIR);
229 }
230
231 static int cpio_mkpipe_line(const char *line)
232 {
233         return cpio_mkgeneric_line(line, GT_PIPE);
234 }
235
236 static int cpio_mksock_line(const char *line)
237 {
238         return cpio_mkgeneric_line(line, GT_SOCK);
239 }
240
241 static 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];
246
247         if (dev_type == 'b')
248                 mode |= S_IFBLK;
249         else
250                 mode |= S_IFCHR;
251
252         if (name[0] == '/')
253                 name++;
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 */
262                 (long) default_mtime,   /* mtime */
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
275 static 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
296 static int cpio_mkfile(const char *name, const char *location,
297                         unsigned int mode, uid_t uid, gid_t gid,
298                         unsigned int nlinks)
299 {
300         char s[256];
301         struct stat buf;
302         unsigned 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         if (buf.st_size > 0xffffffff) {
330                 fprintf(stderr, "%s: Size exceeds maximum cpio file size\n",
331                         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)
339                         size = buf.st_size;
340
341                 if (name[0] == '/')
342                         name++;
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
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) {
376                                 fprintf(stderr, "writing filebuf failed\n");
377                                 goto error;
378                         }
379                         offset += this_read;
380                         size -= this_read;
381                 }
382                 push_pad();
383
384                 name += namesize;
385         }
386         ino++;
387         rc = 0;
388
389 error:
390         if (file >= 0)
391                 close(file);
392         return rc;
393 }
394
395 static char *cpio_replace_env(char *new_location)
396 {
397         char expanded[PATH_MAX + 1];
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);
407         }
408
409         return new_location;
410 }
411
412 static int cpio_mkfile_line(const char *line)
413 {
414         char name[PATH_MAX + 1];
415         char *dname = NULL; /* malloc'ed buffer for hard links */
416         char location[PATH_MAX + 1];
417         unsigned int mode;
418         int uid;
419         int gid;
420         int nlinks = 1;
421         int end = 0, dname_len = 0;
422         int rc = -1;
423
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)) {
427                 fprintf(stderr, "Unrecognized file format '%s'", line);
428                 goto fail;
429         }
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         }
457         rc = cpio_mkfile(dname, cpio_replace_env(location),
458                          mode, uid, gid, nlinks);
459  fail:
460         if (dname_len) free(dname);
461         return rc;
462 }
463
464 static void usage(const char *prog)
465 {
466         fprintf(stderr, "Usage:\n"
467                 "\t%s [-t <timestamp>] <cpio_list>\n"
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"
473                 "file <name> <location> <mode> <uid> <gid> [<hard links>]\n"
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"
480                 "<name>       name of the file/dir/nod/etc in the archive\n"
481                 "<location>   location of the file in the current filesystem\n"
482                 "             expands shell variables quoted with ${}\n"
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"
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"
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",
503                 prog);
504 }
505
506 static const struct file_handler file_handler_table[] = {
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
533 int 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;
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         }
565
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
576         if (argc - optind != 1) {
577                 usage(argv[0]);
578                 exit(1);
579         }
580         filename = argv[optind];
581         if (!strcmp(filename, "-"))
582                 cpio_list = stdin;
583         else if (!(cpio_list = fopen(filename, "r"))) {
584                 fprintf(stderr, "ERROR: unable to open '%s': %s\n\n",
585                         filename, strerror(errno));
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;
606                         break;
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         }
642         if (ec == 0)
643                 cpio_trailer();
644
645         exit(ec);
646 }