text: /*
   * Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
   *
   * SPDX-License-Identifier: BSD-2-Clause
   */
  
  /* Tool for removing metadata from a CPIO archive.
   *
   * The motivation behind this is to work towards idempotent builds. Part of the
   * seL4 build system forms a CPIO archive of ELF files from the host file
   * system. This archive inadvertently includes information like the i-node
   * numbers and modified times of these files. This information is irrelevant at
   * runtime, but causes the resulting image to not be binary identical between
   * otherwise identical builds.
   *
   * The code that follows strips or replaces the following fields from CPIO file
   * entries:
   *  - i-node number
   *  - UID
   *  - GID
   *  - modified time
   */
  
  #define _XOPEN_SOURCE 700
  
  /* We deliberately use seL4's CPIO library rather than libarchive or similar so
   * we have the same interpretation of CPIO files as seL4. This isn't strictly
   * essential, but it's nice for testing the robustness of this library.
   */
  #include <cpio/cpio.h>
  
  #include <assert.h>
  #include <errno.h>
  #include <stdint.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <sys/mman.h>
  
  /* Find the pointer to a CPIO entry header from a pointer to the entry's data.
   * This essentially reverses the transformation in cpio_get_entry.
   */
  static void *get_header(void *data, const char *filename)
  {
      assert((uintptr_t)data % CPIO_ALIGNMENT == 0);
      uintptr_t p = (uintptr_t)data - strlen(filename) - 1
                    - sizeof(struct cpio_header);
      return (void *)(p - (p % CPIO_ALIGNMENT));
  }
  
  int main(int argc, char **argv)
  {
      if (argc != 2) {
          fprintf(stderr, "Usage: %s file\n"
                  " Strip meta data from a CPIO file\n", argv[0]);
          return -1;
      }
  
      FILE *archive = NULL;
      void *p = NULL;
      long len = 0;
  
      archive = fopen(argv[1], "r+");
      if (archive == NULL) {
          perror("failed to open archive");
          goto fail;
      }
  
      /* Determine the size of the archive, as we'll need to mmap the whole
       * thing.
       */
      if (fseek(archive, 0, SEEK_END) != 0) {
          perror("failed to seek archive");
          goto fail;
      }
      len = ftell(archive);
      if (len == -1) {
          perror("failed to read size of archive");
          goto fail;
      }
      if (fseek(archive, 0, SEEK_SET) != 0) {
          perror("failed to return to beginning of archive");
          goto fail;
      }
  
      /* Mmap the file so we can operate on it with libcpio. */
      p = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fileno(archive), 0);
      if (p == MAP_FAILED) {
          perror("failed to mmap archive");
          p = NULL;
          goto fail;
      }
  
      struct cpio_info info = { .file_count = 0 };
      int err = cpio_info(p, &info);
      if (err != 0) {
          fprintf(stderr, "failed to read CPIO info\n");
          goto fail;
      }
  
      for (unsigned int i = 0; i < info.file_count; i++) {
  
          /* Use libcpio to look up the entry. */
          unsigned long size;
          const char *filename;
          void *data = cpio_get_entry(p, i, &filename, &size);
          if (data == NULL) {
              fprintf(stderr, "failed to locate entry %u\n", i);
              goto fail;
          }
  
          /* Reverse the data pointer to a header pointer. */
          struct cpio_header *header = get_header(data, filename);
          assert((uintptr_t)header % CPIO_ALIGNMENT == 0);
  
          /* Synthesise an i-node number. This just needs to be distinct within
           * the archive. I-node numbers <=10 are reserved on certain file
           * systems.
          */
          unsigned int inode = 11 + i;
          snprintf(header->c_ino, sizeof(header->c_ino), "%08x", inode);
  
          /* Set the file owned by 'root'. */
          memset(header->c_uid, 0, sizeof(header->c_uid));
          memset(header->c_gid, 0, sizeof(header->c_gid));
  
          /* Blank the modified time. */
          memset(header->c_mtime, 0, sizeof(header->c_mtime));
      }
  
      munmap(p, len);
      fclose(archive);
  
      return 0;
  
  fail:
      if (p != NULL) {
          munmap(p, len);
      }
      if (archive != NULL) {
          fclose(archive);
      }
      return -1;
  }
  