linux/usr/gen_init_cpio.c
<<
>>
Prefs
   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
  27static unsigned int offset;
  28static unsigned int ino = 721;
  29static time_t default_mtime;
  30static bool do_file_mtime;
  31static bool do_csum = false;
  32
  33struct file_handler {
  34        const char *type;
  35        int (*handler)(const char *line);
  36};
  37
  38static 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
  47static void push_pad (void)
  48{
  49        while (offset & 3) {
  50                putchar(0);
  51                offset++;
  52        }
  53}
  54
  55static 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
  72static void push_hdr(const char *s)
  73{
  74        fputs(s, stdout);
  75        offset += 110;
  76}
  77
  78static 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
 108static 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
 139static 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
 157static 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
 185enum generic_types {
 186        GT_DIR,
 187        GT_PIPE,
 188        GT_SOCK
 189};
 190
 191struct generic_type {
 192        const char *type;
 193        mode_t mode;
 194};
 195
 196static 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
 211static 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
 230static int cpio_mkdir_line(const char *line)
 231{
 232        return cpio_mkgeneric_line(line, GT_DIR);
 233}
 234
 235static int cpio_mkpipe_line(const char *line)
 236{
 237        return cpio_mkgeneric_line(line, GT_PIPE);
 238}
 239
 240static int cpio_mksock_line(const char *line)
 241{
 242        return cpio_mkgeneric_line(line, GT_SOCK);
 243}
 244
 245static 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
 279static 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
 300static 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
 323static 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
 434error:
 435        if (file >= 0)
 436                close(file);
 437        return rc;
 438}
 439
 440static 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
 457static 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
 509static 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
 553static 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
 580int 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