Subversion Repositories QNX 8.QNX8 IFS tool

Rev

Rev 15 | Rev 17 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

  1. // ifstool.c -- portable reimplementation of QNX's mkifs by Pierre-Marie Baty <pm@pmbaty.com>
  2.  
  3. // TODO: preboot file stripping
  4. // TODO: startup file stripping
  5. // TODO: kernel file stripping
  6. // TODO: boot script compiler
  7.  
  8. // standard C includes
  9. #include <stdint.h>
  10. #include <stdbool.h>
  11. #include <stdlib.h>
  12. #include <stdarg.h>
  13. #include <stdio.h>
  14. #include <string.h>
  15. #include <limits.h>
  16. #include <errno.h>
  17. #include <sys/stat.h>
  18. #include <ctype.h>
  19. #include <time.h>
  20.  
  21. // platform-specific includes
  22. #ifdef _MSC_VER
  23. #include <io.h>
  24. #include <direct.h>
  25. #include <sys/utime.h>
  26. #else // !_MSC_VER
  27. #include <sys/param.h>
  28. #include <unistd.h>
  29. #include <utime.h>
  30. #endif // _MSC_VER
  31.  
  32. // own includes
  33. #include "buffer.h"
  34. #include "sha512.h"
  35. #include "elffile.h"
  36. #include "ifsfile.h"
  37. #include "utility.h"
  38.  
  39.  
  40. // compiler-specific glue
  41. #ifndef _MSC_VER
  42. #define sscanf_s sscanf // WARNING: TRUE FOR THIS FILE ONLY!
  43. #endif // !_MSC_VER
  44.  
  45.  
  46. // libasan (Address Sanitizer) options: this is not a daemon, so I don't care about leaks: they will be recovered by the OS at program exit
  47. const char *__asan_default_options () { return ("detect_leaks=0"); }
  48.  
  49.  
  50. // placeholder value
  51. #define WILL_BE_FILLED_LATER 0xbaadf00d // urgh
  52.  
  53.  
  54. // miscellaneous macros
  55. #define ROUND_TO_UPPER_MULTIPLE(val,multiple) ((((val) + (size_t) (multiple) - 1) / (multiple)) * (multiple)) // note that val is being evaluated once, so it can be the result of a function call
  56. #ifdef _WIN32
  57. #define IS_DIRSEP(c) (((c) == '/') || ((c) == '\\')) // platform-specific directory separator, Win32 variant
  58. #define PATH_SEP ";" // platform-specific PATH element separator (as string), Win32 variant
  59. #else // !_WIN32, thus POSIX
  60. #define IS_DIRSEP(c) ((c) == '/') // platform-specific directory separator, UNIX variant
  61. #define PATH_SEP ":" // platform-specific PATH element separator (as string), UNIX variant
  62. #endif // _WIN32
  63. #define RECORD_SEP "\x1e" // arbitrarily-chosen ASCII record separator, as a C string suitable for e.g. strtok()
  64.  
  65.  
  66. #define INITIAL_STARTUP_SCRIPT \
  67.    /* procmgr_symlink /proc/boot/ldqnx-64.so.2 /usr/lib/ldqnx-64.so.2 */ \
  68.    "\x34\x00" /*size*/ "\x04" /*type*/ "\x00" /*spare*/ "/proc/boot/ldqnx-64.so.2\0" "/usr/lib/ldqnx-64.so.2\0" \
  69.    /* sh /proc/boot/startup.sh */ \
  70.    "\x88\x00" /*size*/ "\x00" /*type*/ "\x00" /*spare*/ "\x00" /*CPU mask*/ "\x00" /*flags*/ "\x00\x00" /*reserved*/ "\x00" /*policy*/ "\x00" /*priority*/ "\02" /*argc*/ "\x02" /*envc*/ "sh\0" /*executable*/ "sh\0" "/proc/boot/startup.sh\0" /*argv*/ "PATH=/sbin:/usr/sbin:/bin:/usr/bin:/proc/boot\0" "LD_LIBRARY_PATH=/proc/boot:/lib:/lib/dll:/usr/lib\0" /*envp*/ \
  71.    /* display_msg "Startup complete */ \
  72.    "\x18\x00" /*size*/ "\x03" /*type*/ "\x00" /*spare*/ "Startup complete\n\0" "\x00\00" /*padding*/ \
  73.    /* trailer */ \
  74.    "\x00\x00\x00\x00"
  75.  
  76.  
  77. // IFS directory entry insertion parameters structure type definition
  78. typedef struct parms_s
  79. {
  80.    int dperms; // directory permissions (e.g. 0755)
  81.    int perms; // file permissions (e.g. 0644)
  82.    int uid; // owner user ID (e.g. 0 = root)
  83.    int gid; // owner group ID (e.g. 0 = root)
  84.    int st_mode; // entry type (e.g. S_IFREG for files) and permissions
  85.    uint32_t mtime; // entry's modification time POSIX timestamp - set to UINT32_MAX to use the concerned files' mtime on the build host
  86.    uint32_t mtime_for_inline_files; // same as above but only for files that don't exist on the build host (i.e. files with an explicit content blob)
  87.    char prefix[MAXPATHLEN]; // install path (e.g. "proc/boot")
  88.    bool should_follow_symlinks; // follow symlinks
  89.    bool should_autosymlink_dylib; // dynamic libraries should be written under their official SONAME and a named symlink be created pointing at them
  90.    bool should_keep_ld_output; // whether to keep .sym files produced by ld calls, togglable by the [+keeplinked] attribute
  91.    bool is_compiled_bootscript; // entry has [+script] attribute
  92.    int extra_ino_flags; // bitmap of extra inode flags (IFS_INO_xxx)
  93.    char search[10 * MAXPATHLEN]; // binary search path (the default one will be constructed at startup)
  94.  
  95.    buffer_t data; // the resolved file's own data bytes
  96. } parms_t;
  97.  
  98.  
  99. // exported globals
  100. int verbose_level = 1; // verbosity level, can be increased with multiple -v[...] flags
  101.  
  102.  
  103. // global variables used in this module only
  104. static char line_buffer[4096]; // scrap buffer for the IFS build file parser
  105. static uint32_t image_base = 4 * 1024 * 1024; // default image base, as per QNX docs -- can be changed with the [image=XXXX] attribute in the IFS build file
  106. static uint32_t image_end = UINT32_MAX; // default image end (no limit)
  107. static uint32_t image_maxsize = UINT32_MAX; // default image max size (no limit)
  108. static uint32_t image_totalsize = 0; // image total size, measured once all the blocks have been written to the output IFS file
  109. static uint32_t image_align = 4; // default image alignment, as per QNX docs
  110. static uint32_t image_kernel_ino = 0;
  111. static uint32_t image_bootscript_ino = 0;
  112. #if defined(__x86_64__)
  113. static char image_processor[16] = "x86_64"; // default CPU type for which this image is built, either "x86_64" or "aarch64le" (will be used to find out the right include paths under $QNX_TARGET)
  114. #elif defined(__aarch64__)
  115. static char image_processor[16] = "aarch64le"; // default CPU type for which this image is built, either "x86_64" or "aarch64le" (will be used to find out the right include paths under $QNX_TARGET)
  116. #else // unknown platform
  117. #error Please port ifstool to this platform
  118. #endif
  119. static char *buildfile_pathname = NULL; // pathname of IFS build file
  120. static char *current_line = NULL; // copy of current line in IFS build file
  121. static int lineno = 0; // current line number in IFS build file
  122. static char *QNX_TARGET = NULL; // value of the $QNX_TARGET environment variable
  123. static char *MKIFS_PATH = NULL; // value of the $MKIFS_PATH environment variable (may contain references to $QNX_TARGET). Initialized by this program if empty.
  124.  
  125. // bootable IFS support
  126. static char *bootfile_pathname = NULL;           // HACK: pathname to bootcode binary blob file to put at the start of a bootable IFS
  127. static size_t bootfile_size = 0;                 // HACK: size of the bootcode binary blob file to put at the start of a bootable IFS
  128. static char *startupfile_pathname = NULL;        // HACK: pathname to precompiled startup file blob to put in the startup header of a bootable IFS
  129. static size_t startupfile_ep_from_imagebase = 0; // HACK: startup code entrypoint offset from image base for a bootable IFS
  130. static char *kernelfile_pathname = NULL;         // HACK: pathname to precompiled kernel file blob to put in a bootable IFS
  131. static size_t kernelfile_offset = 0;             // HACK: kernel file offset in bootable IFS
  132.  
  133.  
  134. // exported function prototypes
  135. int32_t update_checksum (const void *data, const size_t data_len, const bool is_foreign_endianness); // compute an IFS image or startup checksum to store in the trailer
  136.  
  137.  
  138. // prototypes of local functions
  139. static long long read_integer (const char *str); // reads an integer number for a string that may be specified in either hex, octal or decimal base, and may have an optional unit suffix (k, m, g, t)
  140. static char *resolve_pathname (const char *pathname, const char *search_path); // locates pathname among MKIFS_PATH and returns a pointer to the resolved pathname (static string)
  141. static elf_section_header_t *elf_get_section_header_by_name (const elf_header_t *elf, const char *section_name); // get a pointer to a named section header in an ELF file
  142. static size_t Buffer_WriteIFSDirectoryEntryAt (buffer_t *ifs_data, const size_t write_offset, const fsentry_t *fsentry); // writes the given filesystem entry (without its contents) to the IFS buffer
  143. static size_t Buffer_AppendIFSFileData (buffer_t *ifs_data, fsentry_t *fsentry); // writes the given filesystem entry's file data (i.e. its contents) to the IFS buffer
  144. static int Buffer_StripELFFile (buffer_t *file, const char *indicative_pathname); // strips an ELF file buffer the way mkifs does it and returns whether it succeeded
  145. static size_t add_fsentry (fsentry_t **fsentries, size_t *fsentry_count, parms_t *entry_parms, const char *stored_pathname, const char *buildhost_pathname); // stack up a new filesystem entry
  146. static int fsentry_compare_pathnames_cb (const void *a, const void *b); // qsort() comparison callback that sorts filesystem entries by pathnames
  147. static void update_MKIFS_PATH (const char *processor);
  148.  
  149.  
  150. // imported function prototypes
  151. extern int dump_ifs_info (const char *ifs_pathname, bool want_everything); // [implemented in ifsdump.c] dumps detailed info about a particular IFS file on the standard output, returns 0 on success and >0 on error
  152. extern int dump_ifs_contents (const char *ifs_pathname, const char *outdir); // [implemented in ifsdump.c] dumps the IFS filesystem contents in outdir, returns 0 on success and >0 on error
  153. extern int dump_file_hex (const char *pathname); // [implemented in ifsdump.c] dumps the contents of pathname to stdout in mixed hexadecimal + ASCII (hex editor) format
  154.  
  155.  
  156. int32_t update_checksum (const void *data, const size_t data_len, const bool is_foreign_endianness)
  157. {
  158.    // computes the checksum of an IFS image or startup section, i.e. from the start of the header to the end of the trailer minus the last 4 bytes where the checksum is stored
  159.  
  160.    uint8_t accumulator[4] = { 0, 0, 0, 0 };
  161.    const char *current_char_ptr;
  162.    int32_t image_cksum;
  163.    size_t i;
  164.  
  165.    image_cksum = 0;
  166.    current_char_ptr = data;
  167.    for (i = 0; i < data_len; i++)
  168.    {
  169.       accumulator[i % 4] = *current_char_ptr;
  170.       if (i % 4 == 3)
  171.          if (is_foreign_endianness)
  172.             image_cksum += (accumulator[3] << 0) + (accumulator[2] << 8) + (accumulator[1] << 16) + (accumulator[0] << 24);
  173.          else
  174.             image_cksum += (accumulator[0] << 0) + (accumulator[1] << 8) + (accumulator[2] << 16) + (accumulator[3] << 24);
  175.       current_char_ptr++;
  176.    }
  177.  
  178.    return (is_foreign_endianness ? __builtin_bswap32 (-image_cksum) : -image_cksum);
  179. }
  180.  
  181.  
  182. static long long read_integer (const char *str)
  183. {
  184.    // reads a number for a string that may be specified in either hex, octal or decimal base, and may have an optional unit suffix (k, m, g, t)
  185.  
  186.    char *endptr = NULL;
  187.    long long ret = strtoll (str, &endptr, 0); // use strtoll() to handle hexadecimal (0x...), octal (0...) and decimal (...) bases
  188.    if (endptr != NULL)
  189.    {
  190.       if      ((*endptr == 'k') || (*endptr == 'K')) ret *= (size_t) 1024;
  191.       else if ((*endptr == 'm') || (*endptr == 'M')) ret *= (size_t) 1024 * 1024;
  192.       else if ((*endptr == 'g') || (*endptr == 'G')) ret *= (size_t) 1024 * 1024 * 1024;
  193.       else if ((*endptr == 't') || (*endptr == 'T')) ret *= (size_t) 1024 * 1024 * 1024 * 1024; // future-proof enough, I suppose?
  194.    }
  195.    return (ret);
  196. }
  197.  
  198.  
  199. static char *resolve_pathname (const char *pathname, const char *search_path)
  200. {
  201.    // locates pathname among search path and returns resolved pathname (static buffer) or NULL.
  202.  
  203.    static thread_local char *resolved_pathname = NULL;
  204.  
  205.    struct stat stat_buf;
  206.    const char *nextsep;
  207.    const char *token;
  208.  
  209.    // initial allocation (per thread)
  210.    if (resolved_pathname == NULL)
  211.    {
  212.       resolved_pathname = malloc (MAXPATHLEN);
  213.       ASSERT_WITH_ERRNO (resolved_pathname);
  214.    }
  215.  
  216.    // is it an absolute pathname (POSIX and Windows variants) ?
  217.    if (IS_DIRSEP (pathname[0]) || (isalpha (pathname[0]) && (pathname[1] == ':') && IS_DIRSEP (pathname[2])))
  218.       strcpy_s (resolved_pathname, MAXPATHLEN, pathname); // in this case, it MUST exist at its designated location (either absolute or relative to the current working directory)
  219.    else // the path is relative, search it among the search paths we have
  220.    {
  221.       // construct a potential final path using each element of the search path
  222.       token = (*search_path != 0 ? search_path : NULL);
  223.       nextsep = (token != NULL ? &token[strcspn (token, PATH_SEP)] : NULL);
  224.       while (token != NULL)
  225.       {
  226.          sprintf_s (resolved_pathname, MAXPATHLEN, "%.*s/%s", (int) (nextsep - token), token, pathname);
  227.          if ((stat (resolved_pathname, &stat_buf) == 0) && S_ISREG (stat_buf.st_mode))
  228.             return (resolved_pathname); // if a file can indeed be found at this location, stop searching
  229.  
  230.          token = (*nextsep != 0 ? nextsep + 1 : NULL);
  231.          nextsep = (token != NULL ? &token[strcspn (token, PATH_SEP)] : NULL);
  232.       }
  233.    }
  234.  
  235.    errno = ENOENT; // we exhausted all possibilities
  236.    return (NULL); // file not found, return with ENOENT
  237. }
  238.  
  239.  
  240. static size_t Buffer_WriteIFSDirectoryEntryAt (buffer_t *ifs, const size_t write_offset, const fsentry_t *fsentry)
  241. {
  242.    // writes a directory entry in the image filesystem buffer pointed to by ifs at write_offset (or fakes so if ifs is NULL)
  243.    // and return the number of bytes written (or that would have been written)
  244.  
  245.    static const uint8_t zeropad_buffer[] = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
  246.  
  247.    size_t datalen;
  248.    size_t count;
  249.  
  250.    count = 0;
  251.    if (ifs != NULL)
  252.       ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, &fsentry->header, sizeof (fsentry->header))); // write the entry header (PACKED STRUCT)
  253.    count += sizeof (fsentry->header);
  254.    if (S_ISREG (fsentry->header.mode))
  255.    {
  256.       if (ifs != NULL)
  257.          ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, &fsentry->u.file.offset, sizeof (uint32_t))); // write offset
  258.       count += sizeof (uint32_t);
  259.       if (ifs != NULL)
  260.          ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, &fsentry->u.file.size,   sizeof (uint32_t))); // write size
  261.       count += sizeof (uint32_t);
  262.       datalen = strlen (fsentry->u.file.path) + 1;
  263.       if (ifs != NULL)
  264.          ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, fsentry->u.file.path, datalen)); // write null-terminated path (no leading slash)
  265.       count += datalen;
  266.    }
  267.    else if (S_ISDIR (fsentry->header.mode))
  268.    {
  269.       datalen = strlen (fsentry->u.dir.path) + 1;
  270.       if (ifs != NULL)
  271.          ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, fsentry->u.dir.path, datalen)); // write null-terminated path (no leading slash)
  272.       count += datalen;
  273.    }
  274.    else if (S_ISLNK (fsentry->header.mode))
  275.    {
  276.       if (ifs != NULL)
  277.          ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, &fsentry->u.symlink.sym_offset, sizeof (uint16_t))); // write offset
  278.       count += sizeof (uint16_t);
  279.       if (ifs != NULL)
  280.          ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, &fsentry->u.symlink.sym_size,   sizeof (uint16_t))); // write size
  281.       count += sizeof (uint16_t);
  282.       datalen = strlen (fsentry->u.symlink.path) + 1;
  283.       if (ifs != NULL)
  284.          ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, fsentry->u.symlink.path, datalen)); // write null-terminated path (no leading slash)
  285.       count += datalen;
  286.       datalen = strlen (fsentry->u.symlink.contents) + 1;
  287.       if (ifs != NULL)
  288.          ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, fsentry->u.symlink.contents, datalen)); // write null-terminated symlink contents
  289.       count += datalen;
  290.    }
  291.    else
  292.    {
  293.       if (ifs != NULL)
  294.          ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, &fsentry->u.device.dev,  sizeof (uint32_t))); // write dev number
  295.       count += sizeof (uint32_t);
  296.       if (ifs != NULL)
  297.          ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, &fsentry->u.device.rdev, sizeof (uint32_t))); // write rdev number
  298.       count += sizeof (uint32_t);
  299.       datalen = strlen (fsentry->u.device.path) + 1;
  300.       if (ifs != NULL)
  301.          ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, fsentry->u.device.path, datalen)); // write null-terminated path (no leading slash)
  302.       count += datalen;
  303.    }
  304.  
  305.    ASSERT (count <= fsentry->header.size, "attempt to write invalid dirent (claimed size %zd, written size %zd). Aborting.", (size_t) fsentry->header.size, count);
  306.    if (count < fsentry->header.size)
  307.    {
  308.       if (ifs != NULL)
  309.          ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, zeropad_buffer, fsentry->header.size - count)); // pad as necessary
  310.       count += fsentry->header.size - count;
  311.    }
  312.  
  313.    return (count);
  314. }
  315.  
  316.  
  317. static size_t Buffer_AppendIFSFileData (buffer_t *ifs_data, fsentry_t *fsentry)
  318. {
  319.    // writes the given filesystem entry's file data (i.e. its contents) to the IFS buffer
  320.  
  321.    elf_program_header_t *phdr;
  322.    elf_header_t *elf;
  323.    size_t corrective_offset;
  324.    //size_t segment_type;
  325.    size_t segment_size;
  326.    size_t table_index;
  327.    size_t table_count;
  328.    size_t data_offset;
  329.  
  330.    ASSERT (S_ISREG (fsentry->header.mode), "function called for invalid dirent"); // consistency check
  331.    data_offset = ifs_data->size; // see where we are
  332.  
  333.    // is the file we're storing a preprocessed ELF file ?
  334.    if ((fsentry->header.ino & IFS_INO_PROCESSED_ELF)
  335. #ifndef PROCNTO_WIP
  336.       && (strstr (fsentry->u.file.path, "/procnto-smp-instr") == NULL)
  337. #endif // !PROCNTO_WIP
  338.        )
  339.    {
  340.  
  341.       elf = (elf_header_t *) fsentry->u.file.UNSAVED_databuf; // quick access to ELF header
  342.       table_count = ELF_GET_NUMERIC (elf, elf, program_header_table_len); // get the number of program headers
  343.       for (table_index = 0; table_index < table_count; table_index++)
  344.       {
  345.          phdr = (elf_program_header_t *) &fsentry->u.file.UNSAVED_databuf[ELF_GET_NUMERIC (elf, elf, program_header_table_offset) + (size_t) ELF_GET_NUMERIC (elf, elf, program_header_item_size) * table_index]; // quick access to program header
  346.          //segment_type = ELF_GET_NUMERIC (elf, phdr, segment_type); // get segment type
  347.          //if (!((segment_type >= 2) && (segment_type <= 7) || ((segment_type >= 0x6474e550) && (segment_type <= 0x6474e552)) || (segment_type == 0x70000001)))
  348.          //   continue; // NOTE: only certain segments types must be corrected
  349.  
  350.  
  351.          corrective_offset = ELF_GET_NUMERIC (elf, phdr, virtual_addr) - ELF_GET_NUMERIC (elf, phdr, file_offset);
  352.          segment_size = ELF_GET_NUMERIC (elf, phdr, size_in_memory); // get this ELF segment's occupied size in memory
  353.          if (segment_size != 0) // only patch the physical address of segments that have an actual size in memory
  354.             ELF_SET_NUMERIC (elf, phdr, physical_addr, ELF_GET_NUMERIC (elf, phdr, physical_addr) + image_base + data_offset - corrective_offset); // patch the physical address member of the program header table (NOTE: data_offset is the location where the file data is about to be written)
  355.       }
  356.    }
  357.  
  358.    ASSERT_WITH_ERRNO (Buffer_Append (ifs_data, fsentry->u.file.UNSAVED_databuf, fsentry->u.file.size)); // write file data blob
  359.    return (ifs_data->size - data_offset); // return the number of bytes written
  360. }
  361.  
  362.  
  363. static inline size_t Buffer_LocateOrAppendIfNecessaryAndReturnOffsetOf (buffer_t *buffer, const char *str)
  364. {
  365.    // helper function used in add_fsentry(): locates or appends str to buffer and returns its relative offset in the buffer
  366.  
  367.    size_t str_len_including_terminator = strlen (str) + 1;
  368.    void *occurrence = Buffer_FindFirst (buffer, str, str_len_including_terminator);
  369.    if (occurrence == NULL)
  370.    {
  371.       ASSERT_WITH_ERRNO (Buffer_Append (buffer, str, str_len_including_terminator));
  372.       occurrence = Buffer_FindFirst (buffer, str, str_len_including_terminator);
  373.       ASSERT_WITH_ERRNO (occurrence);
  374.    }
  375.    return (Buffer_OffsetOf (buffer, occurrence)); // can't fail
  376. }
  377.  
  378.  
  379. static int Buffer_StripELFFile (buffer_t *file, const char *indicative_pathname)
  380. {
  381.    // NOTE: for each ELF file, mkifs
  382.    // -> alters the program header table and offsets each p_addr (physical address) member by <image_base> plus the current file offset (this cannot be done right now, will need to be done once they are known)
  383.    // -> throws away and reconstructs the sections table by keeping only the sections that are in the program header, and writes the section table at the start of the first thrown-away section
  384.    // FIXME: what if a thrown away section is located between two program segments ? are they collapsed, moving the segments beyond it one slot down ?
  385.  
  386.    // reconstructed ELF:
  387.    // ==== START OF FILE ====
  388.    // ELF header
  389.    // program header table
  390.    //  (same sections, just p_addr offset changed)
  391.    // section data 5 (named ".note.gnu.build-id")
  392.    //  "............GNU....ZY.....c.o..l"
  393.    // PROGRAM
  394.    // sections table
  395.    // + section 1: ALL ZEROES
  396.    // + section 2: fileoffs 0x21a8 size 0xfd --> "QNX_info" --> QNX binary description: "NAME=pci_debug2.so.3.0\nDESCRIPTION=PCI Server System Debug Module\nDATE=2023/11/19-10:01:13-EST\nSTATE=lookup\nHOST=docker-n1.bts.rim.net\nUSER=builder\nVERSION=QNXOS_main\nTAGID=QNXOS_800-135\nPACKAGE=com.qnx.qnx800.target.pci.debug/3.0.0.00135T202311191043L\n"
  397.    // + section 3: fileoffs 0x22a5 size 0x1c --> ".gnu_debuglink" --> indicates the debug file and its checksum: "pci_debug2.so.3.0.sym" "\0\0\0" "VX2p"
  398.    // + section 4: fileoffs 0x22c1 size 0x2ad --> "QNX_usage" --> HELP TEXT: "\n-------------------------------------------------------------------------------\n%C\n\nThis module implements debug logging for all PCI server modules. It is\nincluded by setting the environment variable PCI_DEBUG_MODULE and uses\nthe slogger2 APIs.\nNOTE:.On systems which support slogger2, you are encouraged to use this module.instead of pci_debug.so...Release History.---------------..3.0 - This module is functionally equivalent to the previous 2.x version.      however it is incompatible with all pre v3.x PCI components..2.1 - fixes a bug whereby if slogger2 is not running and the PCI_DEBUG_MODULE.      environment variable is set, the client will SIGSEGV..2.0 - initial release.."
  399.    // + section 5: fileoffs 0x190 size 0x32 --> ".note.gnu.build-id" --> GNU build ID
  400.    // + section 6: fileoffs 0x256e size 0x40 --> ".shstrtab" --> sections names strings table
  401.    // section data 2 (named "QNX_info")
  402.    //  (QNX binary description)
  403.    // section data 3 (named ".gnu_debuglink")
  404.    //  (debug file)
  405.    // section data 4 (named "QNX_usage")
  406.    //  (help text)
  407.    // section data 6 (named ".shstrtab")
  408.    //  "\0"
  409.    //  ".shstrtab\0"
  410.    //  "QNX_info\0"
  411.    //  ".gnu_debuglink\0"
  412.    //  "QNX_usage\0"
  413.    //  ".note.gnu.build-id\0"
  414.    // ==== END OF FILE ====
  415.  
  416.    #define ELFHDR ((elf_header_t *) file->bytes) // this convenient definition will make sure the ELF header points at the right location, even after entry_parms.data->byte is reallocated
  417.    #define ADD_SECTION(section_name,section_ptr) do { \
  418.       void *reallocated_ptr = realloc (elf_sections, (elf_section_count + 1) * sizeof (elf_section_t)); \
  419.       ASSERT_WITH_ERRNO (reallocated_ptr); \
  420.       elf_sections = reallocated_ptr; \
  421.       elf_sections[elf_section_count].name = (section_name); \
  422.       Buffer_Initialize (&elf_sections[elf_section_count].data); \
  423.       *(section_ptr) = &elf_sections[elf_section_count]; \
  424.       elf_section_count++; \
  425.    } while (0)
  426.  
  427.    typedef struct elf_section_s
  428.    {
  429.       const char *name;
  430.       elf_section_header_t header;
  431.       buffer_t data;
  432.    } elf_section_t;
  433.  
  434.    static const char *saved_sections[] = { "QNX_info", ".gnu_debuglink", "QNX_usage", ".note.gnu.build-id" };
  435.  
  436.    const elf_program_header_t *phdr;
  437.    const elf_section_header_t *shdr;
  438.    elf_section_t *elf_sections = NULL; // mallocated
  439.    elf_section_t *elf_section = NULL;
  440.    size_t elf_section_count = 0;
  441.    size_t new_shdrtable_offset;
  442.    size_t sectiondata_start;
  443.    size_t sectiondata_size;
  444.    size_t array_index;
  445.    size_t table_index;
  446.    size_t table_count;
  447.    size_t page_size;
  448.  
  449.    // find out the platform page size
  450.    if (ELF_GET_NUMERIC (ELFHDR, ELFHDR, instruction_set) == ELF_MACHINE_X86_64)
  451.       page_size = 4 * 1024; // 4 kb pages on Intel processors
  452.    else if (ELF_GET_NUMERIC (ELFHDR, ELFHDR, instruction_set) == ELF_MACHINE_AARCH64)
  453.       page_size = 16 * 1024; // 16 kb pages on ARM64
  454.    else
  455.    {
  456.       errno = ENOTSUP; // unsupported architecture: set errno to something meaningful
  457.       return (0); // and return an error value
  458.    }
  459.  
  460.    // parse the program header table, and measure the farthest offset known by this table where we'll write the reconstructed section headers table
  461.  
  462.    new_shdrtable_offset = 0;
  463.    table_count = ELF_GET_NUMERIC (ELFHDR, ELFHDR, program_header_table_len);
  464.    for (table_index = 0; table_index < table_count; table_index++)
  465.    {
  466.       phdr = (elf_program_header_t *) &file->bytes[ELF_GET_NUMERIC (ELFHDR, ELFHDR, program_header_table_offset) + (size_t) ELF_GET_NUMERIC (ELFHDR, ELFHDR, program_header_item_size) * table_index]; // quick access to program header
  467.       if (ELF_GET_NUMERIC (ELFHDR, phdr, file_offset) + ELF_GET_NUMERIC (ELFHDR, phdr, size_in_file) > new_shdrtable_offset)
  468.          new_shdrtable_offset = ELF_GET_NUMERIC (ELFHDR, phdr, file_offset) + ELF_GET_NUMERIC (ELFHDR, phdr, size_in_file); // keep track of the farthest offset known by the program headers table
  469.    }
  470.    /*
  471.    size_t new_shdrtable_offset_method2 = 0;
  472.    for (table_index = 0; table_index < table_count; table_index++)
  473.    {
  474.       phdr = (elf_program_header_t *) &file->bytes[ELF_GET_NUMERIC (ELFHDR, ELFHDR, program_header_table_offset) + (size_t) ELF_GET_NUMERIC (ELFHDR, ELFHDR, program_header_item_size) * table_index]; // quick access to program header
  475.       size_t segment_type = ELF_GET_NUMERIC (ELFHDR, phdr, segment_type); // get segment type
  476.       if (!((segment_type >= 2) && (segment_type <= 7)))
  477.          continue; // NOTE: only certain segments types must be corrected
  478.       if (ELF_GET_NUMERIC (ELFHDR, phdr, file_offset) + ELF_GET_NUMERIC (ELFHDR, phdr, size_in_memory) > new_shdrtable_offset_method2)
  479.          new_shdrtable_offset_method2 = ELF_GET_NUMERIC (ELFHDR, phdr, file_offset) + ELF_GET_NUMERIC (ELFHDR, phdr, size_in_memory);
  480.    }
  481.    if (new_shdrtable_offset_method2 > new_shdrtable_offset)
  482.       LOG_DEBUG ("METHOD2: %llx > %llx", new_shdrtable_offset_method2, new_shdrtable_offset);*/
  483.    //new_shdrtable_offset = ROUND_TO_UPPER_MULTIPLE (new_shdrtable_offset, page_size); // round to page size
  484.  
  485.    // re-create the section header table
  486.    ADD_SECTION (".shstrtab", &elf_section); // the first section will be the section names strings table
  487.    ASSERT_WITH_ERRNO (Buffer_InitWithByteArray (&elf_section->data, "\0")); // initialize an empty section headers strings table
  488.    ASSERT_WITH_ERRNO (Buffer_AppendByteArray (&elf_section->data, ".shstrtab\0")); // append ".shstrtab" *INCLUDING* its null terminator
  489.  
  490.    // go through the saved sections array and see if such an ELF section is present in the ELF file
  491.    for (array_index = 0; array_index < sizeof (saved_sections) / sizeof (saved_sections[0]); array_index++)
  492.       if ((shdr = elf_get_section_header_by_name (ELFHDR, saved_sections[array_index])) != NULL) // does this ELF have such a section ?
  493.       {
  494.          ADD_SECTION (saved_sections[array_index], &elf_section); // yes, so save it
  495.          sectiondata_start = ELF_GET_NUMERIC (ELFHDR, shdr, file_offset); // identify section data start offset
  496.          sectiondata_size = ELF_GET_NUMERIC (ELFHDR, shdr, size); // identify section data length
  497.          if (sectiondata_start + sectiondata_size >= new_shdrtable_offset) // should this section be moved ?
  498.             ASSERT_WITH_ERRNO (Buffer_InitWithData (&elf_section->data, &file->bytes[sectiondata_start], sectiondata_size)); // have a copy of this section's data
  499.          else
  500.             Buffer_Initialize (&elf_section->data); // this section is located before the place where we'll write the new section headers table, thus it doesn't need to be moved
  501.          //LOG_DEBUG ("%s: section '%s' start 0x%llx len 0x%llx", indicative_pathname, saved_sections[array_index], (unsigned long long) sectiondata_start, (unsigned long long) sectiondata_size);
  502.  
  503.          // prepare this section's "fixed" header
  504.          memcpy (&elf_section->header, shdr, ELF_STRUCT_SIZE (ELFHDR, shdr)); // have a copy of the old section header first
  505.          ELF_SET_NUMERIC (ELFHDR, &elf_section->header, name_offset, Buffer_LocateOrAppendIfNecessaryAndReturnOffsetOf (&elf_sections[0].data, elf_section->name)); // make sure this section name is in the ELF sections section header strings table and update the relative offset of the section name
  506.       }
  507.  
  508.    // jump over the new section headers table and write the saved sections data after the section headers table
  509.    file->size = new_shdrtable_offset + (1 + elf_section_count) * ELF_STRUCT_SIZE (ELFHDR, &elf_sections[0].header); // start by truncating the ELF file: assume there are no sections beyond the section headers table until known otherwise
  510.    for (table_index = 1; table_index < elf_section_count; table_index++)
  511.    {
  512.       elf_section = &elf_sections[table_index]; // quick access to ELF section about to be written
  513.       if (elf_section->data.bytes != NULL) // was this section data backed up waiting to be relocated ?
  514.       {
  515.          ELF_SET_NUMERIC (ELFHDR, &elf_section->header, file_offset, file->size); // fix section offset
  516.          Buffer_AppendBuffer (file, &elf_section->data); // append this section's data to the ELF file
  517.       }
  518.    }
  519.    // write the section header strings table as the last section
  520.    elf_section = &elf_sections[0]; // quick access to ELF section about to be written
  521.    ELF_SET_NUMERIC (ELFHDR, &elf_section->header, name_offset, Buffer_LocateOrAppendIfNecessaryAndReturnOffsetOf (&elf_sections[0].data, elf_section->name)); // update the relative offset of the section name
  522.    ELF_SET_NUMERIC (ELFHDR, &elf_section->header, type, ELF_SECTIONTYPE_STRINGTABLE); // section type (SHT_STRTAB)
  523.    ELF_SET_NUMERIC (ELFHDR, &elf_section->header, flags, 0); // section flags (we could set SHF_STRINGS i.e. 0x20 here, but mkifs does not, so mimic that)
  524.    ELF_SET_NUMERIC (ELFHDR, &elf_section->header, virtual_addr, 0); // this section does not need to be mapped
  525.    ELF_SET_NUMERIC (ELFHDR, &elf_section->header, file_offset, file->size); // fix section offset
  526.    ELF_SET_NUMERIC (ELFHDR, &elf_section->header, size, elf_sections[0].data.size); // section size
  527.    ELF_SET_NUMERIC (ELFHDR, &elf_section->header, linked_index, 0); // this section is not linked to any other
  528.    ELF_SET_NUMERIC (ELFHDR, &elf_section->header, info, 0); // this section has no additional info
  529.    ELF_SET_NUMERIC (ELFHDR, &elf_section->header, alignment, 1); // this section is byte-aligned
  530.    ELF_SET_NUMERIC (ELFHDR, &elf_section->header, entry_size, 0); // this section is not a table, so entry_size is zero
  531.    Buffer_AppendBuffer (file, &elf_section->data); // append section headers strings table section data to ELF file
  532.  
  533.    // now write the section headers table
  534.    memset (&file->bytes[new_shdrtable_offset], 0, ELF_STRUCT_SIZE (ELFHDR, &elf_sections[0].header)); // the first section header is always zerofilled
  535.    for (table_index = 1; table_index < elf_section_count; table_index++)
  536.       Buffer_WriteAt (file, new_shdrtable_offset + table_index * ELF_STRUCT_SIZE (ELFHDR, &elf_sections[table_index].header), &elf_sections[table_index].header, ELF_STRUCT_SIZE (ELFHDR, &elf_sections[table_index].header)); // write each section header
  537.    Buffer_WriteAt (file, new_shdrtable_offset + table_index * ELF_STRUCT_SIZE (ELFHDR, &elf_sections[table_index].header), &elf_sections[0].header, ELF_STRUCT_SIZE (ELFHDR, &elf_sections[0].header)); // write the section header names section header last
  538.  
  539.    // and finally fix the ELF master header
  540.    ELF_SET_NUMERIC (ELFHDR, ELFHDR, section_header_table_offset, new_shdrtable_offset);
  541.    ELF_SET_NUMERIC (ELFHDR, ELFHDR, section_header_table_len, 1 + elf_section_count); // take in account that the first entry in the section headers table is empty
  542.    ELF_SET_NUMERIC (ELFHDR, ELFHDR, section_header_names_idx, elf_section_count); // the section headers strings table is the last section
  543.  
  544.    // align size with page size (4096 on x86, 16k on ARM), zerofilling the extra space
  545.    ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (file, ROUND_TO_UPPER_MULTIPLE (file->size, page_size)));
  546.  
  547.    // cleanup
  548.    for (table_index = 0; table_index < elf_section_count; table_index++)
  549.       Buffer_Forget (&elf_sections[table_index].data); // free all sections' backing buffers
  550.  
  551.    #undef ELFHDR // undefine the macro that used to always point to the ELF header at the beginning of the file
  552.    return (1); // success
  553. }
  554.  
  555.  
  556. static size_t add_fsentry (fsentry_t **fsentries, size_t *fsentry_count, parms_t *entry_parms, const char *stored_pathname, const char *buildhost_pathname)
  557. {
  558.    static thread_local char *candidate_pathname = NULL;
  559.    static int inode_count = 0; // will be preincremented each time this function is called
  560.  
  561.    const char *original_stored_pathname = NULL;
  562.    buffer_t *shstrtab = NULL;
  563.    const char *canonical_dylib_name;
  564.    const char *dynamic_strings; // strings table of the ".dynamic" section
  565.    const char *last_dirsep;
  566.    char *global_envstring = NULL;
  567.    size_t global_envstring_len = 0;
  568.    char *startup_name = NULL;
  569.    char *procnto_name = NULL;
  570.    char *resolved_pathname;
  571.    void *reallocated_ptr;
  572.    void *old_data;
  573.    struct stat stat_buf;
  574.    fsentry_t *fsentry;
  575.  
  576.    // initial allocation (per thread)
  577.    if (candidate_pathname == NULL)
  578.    {
  579.       candidate_pathname = malloc (MAXPATHLEN);
  580.       ASSERT_WITH_ERRNO (candidate_pathname);
  581.    }
  582.  
  583.    if (S_ISDIR (entry_parms->st_mode)) // are we storing a directory ?
  584.    {
  585.       LOG_INFO ("directory: ino 0x%x uid %d gid %d mode 0%o path \"%s\"", inode_count + 1, entry_parms->uid, entry_parms->gid, entry_parms->st_mode, stored_pathname);
  586.    }
  587.    else if (S_ISREG (entry_parms->st_mode)) // else are we storing a regular file ?
  588.    {
  589.       if (strcmp (stored_pathname, "/proc/boot/boot") == 0) // is it the kernel ?
  590.       {
  591.          // HACK: for now just consider the kernel as a binary blob
  592.          // FIXME: reimplement properly
  593. #ifdef PROCNTO_WIP // FIXME: segment corruption somewhere!
  594.          char *linebit_start;
  595.          char *content_line;
  596.          char *write_ptr;
  597.          char *token;
  598.          char *value;
  599.          char *ctx;
  600.          bool is_quoted_context;
  601.          bool was_string_split;
  602.  
  603.          // parse each line of contents
  604.          ASSERT (entry_parms->data.len > 0, "kernel specification without inline contents");
  605.          for (content_line = strtok_r (entry_parms->data.bytes, "\n", &ctx); content_line != NULL; content_line = strtok_r (NULL, "\n", ctx))
  606.          {
  607.             while (isspace (*content_line))
  608.                content_line++; // skip leading spaces
  609.             if ((*content_line == '#') || (*content_line == 0))
  610.                continue; // skip comments and empty lines
  611.  
  612.             // format of a line: [attributes] [env assignation] [...] [executable] [arg] [...] [comment]
  613.             // example: "[uid=0 gid=0 perms=0700] CONFIG_PATH=/proc/boot:/etc procnto-smp-instr -v -mr -d 0777 -u 0777"
  614.             //LOG_DEBUG ("parsing line: %s", content_line);
  615.  
  616.             // does this line start with an attribute block ?
  617.             if (*content_line == '[')
  618.             {
  619.                content_line++; // skip the leading square bracket
  620.                linebit_start = content_line; // remember where it starts
  621.                is_quoted_context = false; // reach the next unescaped closing square bracket that is not between quotes
  622.                while ((*content_line != 0) && !((*content_line == ']') && (content_line[-1] != '\\') && !is_quoted_context))
  623.                {
  624.                   if (*content_line == '"')
  625.                      is_quoted_context ^= true; // remember when we're between quotes
  626.                   else if (!is_quoted_context && (*content_line == ' '))
  627.                      *content_line = RECORD_SEP[0]; // turn all spaces outside quoted contexts into an ASCII record separator to ease token splitting
  628.                   content_line++; // reach the next unescaped closing square bracket
  629.                }
  630.                if (*content_line != ']')
  631.                {
  632.                   LOG ("warning", 0, "syntax error in \"%s\" line %d: unterminated attributes block (skipping)", buildfile_pathname, lineno);
  633.                   continue; // invalid attribute block, skip line
  634.                }
  635.                *content_line = 0; // end the attribute block so that it is a parsable C string
  636.  
  637.                // now parse the attribute tokens (NOTE: THE LIST OF ALLOWED ATTRIBUTES HERE IS NOT DOCUMENTED)
  638.                token = strtok_r (linebit_start, RECORD_SEP, &ctx);
  639.                while (token != NULL)
  640.                {
  641.                   #define REACH_TOKEN_VALUE() do { value = strchr (token, '=') + 1; if (*value == '"') value++; } while (0)
  642.                   if      (strncmp (token, "uid=",     4) == 0) { REACH_TOKEN_VALUE (); entry_parms->uid     = (int) read_integer (value); }
  643.                   else if (strncmp (token, "gid=",     4) == 0) { REACH_TOKEN_VALUE (); entry_parms->gid     = (int) read_integer (value); }
  644.                   else if (strncmp (token, "perms=",   6) == 0) { REACH_TOKEN_VALUE (); entry_parms->perms   = (int) read_integer (value); }
  645.                   else if (strncmp (token, "prefix=",  7) == 0) { REACH_TOKEN_VALUE (); strcpy (entry_parms->prefix, (*value == '/' ? value + 1 : value)); } // skip possible leading slash in prefix
  646.                   else if (strcmp (token, "+followlink") == 0) entry_parms->should_follow_symlinks = true;
  647.                   else if (strcmp (token, "-followlink") == 0) entry_parms->should_follow_symlinks = false;
  648.                   else if (strcmp (token, "+keeplinked") == 0) entry_parms->should_keep_ld_output = true;
  649.                   else if (strcmp (token, "-keeplinked") == 0) entry_parms->should_keep_ld_output = false;
  650.                   else LOG_WARNING ("unimplemented bootstrap executable attribute in \"%s\" line %d: '%s'", buildfile_pathname, lineno, token);
  651.                   #undef REACH_TOKEN_VALUE
  652.                   token = strtok_r (NULL, RECORD_SEP, &ctx); // proceed to next attribute token
  653.                }
  654.  
  655.                content_line++; // reach the next character
  656.                while ((*content_line != 0) && isspace (*content_line))
  657.                   content_line++; // skip leading spaces
  658.             } // end of "this line starts with an attributes block"
  659.  
  660.             // there's data in this line. We expect an executable OR a variable name. Read it and unescape escaped characters
  661.             while (*content_line != 0)
  662.             {
  663.                linebit_start = content_line; // remember the name starts here
  664.                write_ptr = linebit_start;
  665.                is_quoted_context = (*content_line == '"');
  666.                if (is_quoted_context)
  667.                   content_line++; // skip a possible initial quote in the name
  668.                while ((*content_line != 0) && ((!is_quoted_context && (*content_line != '=') && !isspace (*content_line)) || (is_quoted_context && (*content_line == '"'))))
  669.                {
  670.                   if (*content_line == '\\')
  671.                   {
  672.                      content_line++;
  673.                      *write_ptr++ = *content_line; // unescape characters that are escaped with '\'
  674.                   }
  675.                   else
  676.                      *write_ptr++ = *content_line;
  677.                   content_line++;
  678.                }
  679.  
  680.                // we reached a closing quote, a space OR an equal sign
  681.                if (*content_line == '=')
  682.                {
  683.                   // it's an environment variable assignation
  684.                   *write_ptr++ = *content_line++; // skip the equal sign
  685.                   is_quoted_context = (*content_line == '"');
  686.                   if (is_quoted_context)
  687.                      content_line++; // skip a possible initial quote in the value
  688.                   while ((*content_line != 0) && ((!is_quoted_context && (*content_line != '=') && !isspace (*content_line)) || (is_quoted_context && (*content_line == '"'))))
  689.                   {
  690.                      if (*content_line == '\\')
  691.                      {
  692.                         content_line++;
  693.                         *write_ptr++ = *content_line; // unescape characters that are escaped with '\'
  694.                      }
  695.                      else
  696.                         *write_ptr++ = *content_line;
  697.                      content_line++;
  698.                   }
  699.                   if (*write_ptr != 0)
  700.                   {
  701.                      *write_ptr = 0; // terminate the string if necessary
  702.                      was_string_split = true;
  703.                   }
  704.                   else
  705.                      was_string_split = false;
  706.                   if (is_quoted_context && (*content_line == '"'))
  707.                      content_line++; // skip a possible final quote
  708.                   while ((*content_line != 0) && isspace (*content_line))
  709.                      content_line++; // skip spaces
  710.  
  711.                   // now linebit_start is of the form "NAME=VALUE"
  712.                   LOG_DEBUG ("assignation: [%s]", linebit_start);
  713.  
  714.                   // TODO: grow global_envstring
  715.  
  716.                   //reallocated_ptr = realloc (global_envstring, global_envstring_len + strlen ())
  717.  
  718.                   if (was_string_split)
  719.                      *write_ptr = ' '; // restore string continuity for parsing to continue
  720.                   while ((*content_line != 0) && isspace (*content_line))
  721.                      content_line++; // skip spaces
  722.                }
  723.                else // it's either a closing quote or a space
  724.                {
  725.                   *write_ptr = 0; // terminate the string
  726.                   if (is_quoted_context && (*content_line == '"'))
  727.                      content_line++; // skip a possible final quote
  728.  
  729.                   LOG_DEBUG ("exe name: [%s]", linebit_start);
  730.  
  731.                   while ((*content_line != 0) && isspace (*content_line))
  732.                      content_line++; // skip leading spaces
  733.  
  734.                   // it's an executable name. As per QNX docs, the first executable must be startup-*, the last executable must be procnto.
  735.                   if (startup_name == NULL)
  736.                      startup_name = strdup (linebit_start);
  737.                   else
  738.                   {
  739.                      if (procnto_name != NULL)
  740.                         free (procnto_name);
  741.                      procnto_name = strdup (linebit_start);
  742.                   }
  743.  
  744.                   if ((*content_line == '#') || (*content_line == 0))
  745.                      break; // if we reach the end of the line, stop parsing
  746.  
  747.                   // what comes after now must be optional arguments
  748.                   while ((*content_line != 0) && isspace (*content_line))
  749.                      content_line++; // skip leading spaces
  750.  
  751.                   // FIXME: parse executable command-line arguments
  752.  
  753.                   break; // stop parsing once all the arguments have been read
  754.                }
  755.             }
  756.          } // end of parsing
  757.          free (entry_parms->data.bytes); // free the inline specification once it's parsed
  758.          entry_parms->data.bytes = NULL;
  759.          entry_parms->data.len = 0;
  760.  
  761.          ASSERT (startup_name && *startup_name, "the QNX startup executable (startup-*) is missing in this bootstrap inline specification");
  762.          ASSERT (procnto_name && *procnto_name, "the QNX kernel (procnto-*) is missing in this bootstrap inline specification");
  763.  
  764.          // now we know which startup and procnto executables to use
  765.          LOG_DEBUG ("Startup: %s", startup_name);
  766.          LOG_DEBUG ("Kernel: %s", procnto_name);
  767.  
  768.          sprintf (candidate_pathname, "%s/%s", entry_parms->prefix, procnto_name); // fix the entry name
  769.          stored_pathname = candidate_pathname;
  770.          entry_parms->extra_ino_flags |= /*IFS_INO_PROCESSED_ELF | */IFS_INO_BOOTSTRAP_EXE; // procnto needs to have these flags stamped on the inode
  771.          entry_parms->st_mode = S_IFREG | entry_parms->perms; // apply specified procnto permissions
  772.          image_kernel_ino = entry_parms->extra_ino_flags | (inode_count + 1);
  773.  
  774.          static thread_local char linker_pathname[MAXPATHLEN] = "";
  775.          static thread_local char linker_sysroot_arg[MAXPATHLEN] = "";
  776.          static thread_local char linker_script_pathname_arg[MAXPATHLEN] = "";
  777.          static thread_local char procnto_buildhost_pathname[MAXPATHLEN] = "";
  778.          static thread_local char procnto_sym_filename[MAXPATHLEN] = "";
  779.  
  780.          // construct the arguments that are based on environment variables (infer QNX_HOST from QNX_TARGET)
  781. #if defined(_WIN32)
  782.          sprintf (linker_pathname, "%s/../../host/win64/x86_64/usr/bin/%s-ld.exe", QNX_TARGET, (strcmp (image_processor, "x86_64") == 0 ? "x86_64-pc-nto-qnx8.0.0" : "aarch64-unknown-nto-qnx8.0.0")); // Win32: note the .exe extension
  783. #elif defined(__linux__)
  784.          sprintf (linker_pathname, "%s/../../host/linux/x86_64/usr/bin/%s-ld", QNX_TARGET, (strcmp (image_processor, "x86_64") == 0 ? "x86_64-pc-nto-qnx8.0.0" : "aarch64-unknown-nto-qnx8.0.0"));
  785. #elif defined(__QNXNTO__)
  786.          sprintf (linker_pathname, "%s/../../host/qnx8/x86_64/usr/bin/%s-ld", QNX_TARGET, (strcmp (image_processor, "x86_64") == 0 ? "x86_64-pc-nto-qnx8.0.0" : "aarch64-unknown-nto-qnx8.0.0"));
  787. #else // wtf are you building this on?
  788. #error Please port the GNU linker x86_64-pc-nto-qnx8.0.0-ld and aarch64-unknown-nto-qnx8.0.0-ld to your host architecture first before compiling ifstool.
  789. #endif
  790.          ASSERT (access (linker_pathname, 0) == 0, "host cross-linker for QNX8 \"%s\" not found", linker_pathname);
  791.          sprintf (linker_sysroot_arg, "--sysroot=%s/%s/", QNX_TARGET, image_processor);
  792.          sprintf (linker_script_pathname_arg, "-T%s/%s/lib/nto.link", QNX_TARGET, image_processor);
  793.  
  794.          resolved_pathname = resolve_pathname (procnto_name, (entry_parms->search[0] != 0 ? entry_parms->search : MKIFS_PATH)); // locate the procnto kernel location
  795.          ASSERT (resolved_pathname, "QNX kernel \"%s\" not found in search path", procnto_name);
  796.          strcpy (procnto_buildhost_pathname, resolved_pathname);
  797.  
  798.          sprintf (procnto_sym_filename, "%s.sym", procnto_name);
  799.  
  800.          const char *linker_argv[] = // construct the linker invokation argv
  801.          {
  802.             strrchr (linker_pathname, '/') + 1, // "${TARGET_TRIPLE}-ld"
  803.             linker_sysroot_arg, // "--sysroot=${QNX_TARGET}/${TARGET_CPU}/"
  804.             linker_script_pathname_arg, // "-T${QNX_TARGET}/${TARGET_CPU}/lib/nto.link"
  805.             "--section-start",
  806.             ".text=0xffff800000001000",
  807.             "--no-relax",
  808.             procnto_buildhost_pathname, // "${QNX_TARGET}/${TARGET_CPU}/boot/sys/procnto-smp-instr"
  809.             "-o",
  810.             procnto_sym_filename, // "procnto-smp-instr.sym"
  811.             NULL
  812.          };
  813.          if (verbose_level > 2)
  814.          {
  815.             fprintf (stderr, "ifstool: calling:");
  816.             for (table_index = 0; table_index < sizeof (linker_argv) / sizeof (linker_argv[0]) - 1; table_index++)
  817.                fprintf (stderr, " '%s'", linker_argv[table_index]);
  818.             fputc ('\n', stderr);
  819.          }
  820.          _spawnv (_P_WAIT, linker_pathname, linker_argv); // spawn the linker and produce a stripped procnto (wait for completion)
  821.          if (!Buffer_ReadFromFile (&entry_parms->data, procnto_sym_filename)) // load the output file
  822.             DIE_WITH_EXITCODE (1, "the host cross-linker failed to produce a readable stripped \"%s\" kernel: %s", procnto_sym_filename, strerror (errno));
  823.          if (!entry_parms->should_keep_ld_output)
  824.             unlink (procnto_sym_filename); // remove the linker output file if we want to
  825. #else // !PROCNTO_WIP
  826.          /* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK */
  827.          /* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK */
  828.          /* HACK */
  829.          /* HACK */ sprintf_s (candidate_pathname, MAXPATHLEN, "%s/procnto-smp-instr", entry_parms->prefix); // HACK: fix the entry name
  830.          /* HACK */ stored_pathname = candidate_pathname;
  831.          /* HACK */ entry_parms->extra_ino_flags |= IFS_INO_PROCESSED_ELF | IFS_INO_BOOTSTRAP_EXE; // procnto needs to have these flags stamped on the inode
  832.          /* HACK */ entry_parms->st_mode = S_IFREG | 0700; // procnto requires 0700 permissions
  833.          /* HACK */ image_kernel_ino = entry_parms->extra_ino_flags | (inode_count + 1);
  834.          /* HACK */ free (entry_parms->data.bytes); // discard inline contents
  835.          /* HACK */ Buffer_Initialize (&entry_parms->data);
  836.          /* HACK */ if (!Buffer_ReadFromFile (&entry_parms->data, kernelfile_pathname)) // read kernel file as a precompiled binary blob
  837.          /* HACK */ {
  838.          /* HACK */    fprintf (stderr, "fatal error: unable to read precompiled kernel file \"%s\" specified in --kernelfile argument\n", kernelfile_pathname);
  839.          /* HACK */    exit (1);
  840.          /* HACK */ }
  841.          /* HACK */
  842.          /* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK */
  843.          /* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK */
  844. #endif // PROCNTO_WIP
  845.       }
  846.       else if (entry_parms->is_compiled_bootscript) // else is it a startup script ?
  847.          image_bootscript_ino = inode_count + 1; // save boot script inode number for image header
  848.  
  849.       // do we already know the data for this data blob ?
  850.       if (entry_parms->data.bytes != NULL)
  851.       {
  852.          entry_parms->mtime = entry_parms->mtime_for_inline_files; // if so, set it a mtime equal to the mtime to use for inline files
  853.          LOG_INFO ("file: ino 0x%x uid %d gid %d mode 0%o path \"%s\" blob (len %zd)", entry_parms->extra_ino_flags | (inode_count + 1), entry_parms->uid, entry_parms->gid, entry_parms->st_mode, stored_pathname, entry_parms->data.size);
  854.       }
  855.       else if (buildhost_pathname != NULL) // else was a source file pathname supplied ?
  856.       {
  857.          resolved_pathname = resolve_pathname (buildhost_pathname, (entry_parms->search[0] != 0 ? entry_parms->search : MKIFS_PATH)); // locate the file
  858.          if (resolved_pathname == NULL)
  859.             DIE_WITH_EXITCODE (1, "filesystem entry \"%s\" specified in \"%s\" line %d not found on build host: %s", buildhost_pathname, buildfile_pathname, lineno, strerror (errno));
  860.          if (!Buffer_ReadFromFile (&entry_parms->data, resolved_pathname))
  861.             DIE_WITH_EXITCODE (1, "filesystem entry \"%s\" specified in \"%s\" line %d can't be read: %s", buildhost_pathname, buildfile_pathname, lineno, strerror (errno));
  862.          stat (resolved_pathname, &stat_buf); // can't fail, since we could read it
  863.          if (entry_parms->mtime == UINT32_MAX)
  864.             entry_parms->mtime = (uint32_t) stat_buf.st_mtime;
  865.          LOG_INFO ("file: ino 0x%x uid %d gid %d mode 0%o path \"%s\" buildhost_file \"%s\" (len %zd)", inode_count + 1, entry_parms->uid, entry_parms->gid, entry_parms->st_mode, stored_pathname, buildhost_pathname, entry_parms->data.size);
  866.       }
  867.  
  868.       // is the file we're storing an ELF file ?
  869.       #define ELFHDR ((elf_header_t *) entry_parms->data.bytes) // this convenient definition will make sure the ELF header points at the right location, even after entry_parms.data->byte is reallocated
  870.       if ((entry_parms->data.size > 52) // file is big enough to contain an ELF header
  871.           && (memcmp (ELF_GET_STRING (ELFHDR, ELFHDR, magic), ELF_MAGIC_STR, 4) == 0)) // file starts with the ELF magic
  872.       {
  873.          // is the file we're storing a relocatable executable (i.e. a dynamic library) and should we check for its canonical name ?
  874.          if ((ELF_GET_NUMERIC (ELFHDR, ELFHDR, type) == ELF_TYPE_DYNAMICLIB) && entry_parms->should_autosymlink_dylib)
  875.          {
  876.             // locate the sections we need (the dynamic section and its strings table)
  877.             const elf_section_header_t *shdr_dynamic = elf_get_section_header_by_name (ELFHDR, ".dynamic");
  878.             const elf_section_header_t *shdr_dynstr = elf_get_section_header_by_name (ELFHDR, ".dynstr");
  879.  
  880.             // make sure we have both the dynamic section header and its own strings table header
  881.             if ((shdr_dynamic != NULL) && (shdr_dynstr != NULL))
  882.             {
  883.                dynamic_strings = (char *) &entry_parms->data.bytes[ELF_GET_NUMERIC (ELFHDR, shdr_dynstr, file_offset)]; // quick access to dynamic sections strings table
  884.  
  885.                // walk through the dynamic section, look for the DT_SONAME entry
  886.                canonical_dylib_name = NULL; // assume none until told otherwise
  887.                for (elf_dynamic_section_entry_t *dynamic_entry = (elf_dynamic_section_entry_t *) &entry_parms->data.bytes[ELF_GET_NUMERIC (ELFHDR, shdr_dynamic, file_offset)];
  888.                     (ELF_GET_NUMERIC (ELFHDR, dynamic_entry, tag) != ELF_DT_NULL);
  889.                     dynamic_entry = (elf_dynamic_section_entry_t *) ((uint8_t *) dynamic_entry + ELF_STRUCT_SIZE (ELFHDR, dynamic_entry)))
  890.                   if (ELF_GET_NUMERIC (ELFHDR, dynamic_entry, tag) == ELF_DT_SONAME)
  891.                   {
  892.                      canonical_dylib_name = dynamic_strings + ELF_GET_NUMERIC (ELFHDR, dynamic_entry, value);
  893.                      break;
  894.                   }
  895.  
  896.                // do we have it ?
  897.                if ((canonical_dylib_name != NULL) && (canonical_dylib_name[0] != 0))
  898.                {
  899.                   sprintf_s (candidate_pathname, MAXPATHLEN, "%s/%s", entry_parms->prefix, canonical_dylib_name);
  900.                   if (strcmp (candidate_pathname, stored_pathname) != 0) // claimed dylib name differs from passed name ?
  901.                   {
  902.                      original_stored_pathname = stored_pathname; // if so, remember to create a symlink here
  903.                      stored_pathname = candidate_pathname;
  904.                   }
  905.                }
  906.             }
  907.          } // end if the file we're storing is a dylib
  908.  
  909.          // now strip this ELF file if necessary
  910.          if (!(entry_parms->extra_ino_flags & IFS_INO_PROCESSED_ELF))
  911.          {
  912.             Buffer_StripELFFile (&entry_parms->data, stored_pathname); // strip the ELF file à la mkifs
  913.             entry_parms->extra_ino_flags |= IFS_INO_PROCESSED_ELF; // mark this inode as a preprocessed ELF file
  914.          } // end if the file is not yet a processed ELF
  915.       } // end if the file we're storing is an ELF file
  916.       #undef ELFHDR // undefine the macro that used to always point to the ELF header at the beginning of the file
  917.    }
  918.    else if (S_ISLNK (entry_parms->st_mode)) // else are we storing a symbolic link ?
  919.       LOG_INFO ("symlink: ino 0x%x uid %d gid %d mode 0%o path \"%s\" -> \"%s\"", inode_count + 1, entry_parms->uid, entry_parms->gid, entry_parms->st_mode, stored_pathname, entry_parms->data.bytes);
  920.    else // we must be storing a FIFO
  921.    {
  922.       if (strchr (entry_parms->data.bytes, ':') == NULL)
  923.          DIE_WITH_EXITCODE (1, "device entry \"%s\" malformed (no 'dev:rdev' pair)", stored_pathname);
  924.       LOG_INFO ("fifo: ino 0x%x uid %d gid %d mode 0%o path \"%s\" dev:rdev %s)", inode_count + 1, entry_parms->uid, entry_parms->gid, entry_parms->st_mode, stored_pathname, entry_parms->data.bytes);
  925.    }
  926.  
  927.    // grow filesystem entries array to hold one more slot
  928.    reallocated_ptr = realloc (*fsentries, (*fsentry_count + 1) * sizeof (fsentry_t)); // attempt to reallocate
  929.    ASSERT_WITH_ERRNO (reallocated_ptr);
  930.    *fsentries = reallocated_ptr; // save reallocated pointer
  931.    fsentry = &(*fsentries)[*fsentry_count]; // quick access to fs entry slot
  932.    fsentry->header.extattr_offset = 0;
  933.    fsentry->header.ino = entry_parms->extra_ino_flags | (++inode_count);
  934.    fsentry->header.mode = entry_parms->st_mode;
  935.    fsentry->header.gid = entry_parms->gid;
  936.    fsentry->header.uid = entry_parms->uid;
  937.    fsentry->header.mtime = (entry_parms->mtime == UINT32_MAX ? (uint32_t) time (NULL) : entry_parms->mtime);
  938.    if (S_ISDIR (entry_parms->st_mode))
  939.    {
  940.       fsentry->u.dir.path = strdup (stored_pathname[0] == '/' ? &stored_pathname[1] : stored_pathname);
  941.  
  942.       fsentry->header.size = (uint16_t) ROUND_TO_UPPER_MULTIPLE (sizeof (fsentry->header) + strlen (fsentry->u.dir.path) + 1, image_align); // now we can set the size
  943.       fsentry->UNSAVED_was_data_written = true; // no data to save
  944.    }
  945.    else if (S_ISREG (entry_parms->st_mode))
  946.    {
  947.       fsentry->u.file.offset = WILL_BE_FILLED_LATER; // will be filled later in main() when the file's data blob will be written to the output file
  948.       fsentry->u.file.size = (uint32_t) entry_parms->data.size;
  949.       fsentry->u.file.path = strdup (stored_pathname[0] == '/' ? &stored_pathname[1] : stored_pathname);
  950.       fsentry->u.file.UNSAVED_databuf = malloc (entry_parms->data.size);
  951.       ASSERT_WITH_ERRNO (fsentry->u.file.UNSAVED_databuf);
  952.       memcpy (fsentry->u.file.UNSAVED_databuf, entry_parms->data.bytes, entry_parms->data.size);
  953.  
  954.       fsentry->header.size = (uint16_t) ROUND_TO_UPPER_MULTIPLE (sizeof (fsentry->header) + sizeof (uint32_t) + sizeof (uint32_t) + strlen (fsentry->u.file.path) + 1, image_align); // now we can set the size
  955.       fsentry->UNSAVED_was_data_written = false; // there *IS* data to save
  956.    }
  957.    else if (S_ISLNK (entry_parms->st_mode))
  958.    {
  959.       fsentry->u.symlink.sym_offset = (uint16_t) (strlen (stored_pathname[0] == '/' ? &stored_pathname[1] : stored_pathname) + 1);
  960.       fsentry->u.symlink.sym_size = (uint16_t) entry_parms->data.size;
  961.       fsentry->u.symlink.path = strdup (stored_pathname[0] == '/' ? &stored_pathname[1] : stored_pathname);
  962.       fsentry->u.symlink.contents = strdup (entry_parms->data.bytes);
  963.       ASSERT_WITH_ERRNO (fsentry->u.symlink.contents);
  964.  
  965.       fsentry->header.size = (uint16_t) ROUND_TO_UPPER_MULTIPLE (sizeof (fsentry->header) + sizeof (uint16_t) + sizeof (uint16_t) + (size_t) fsentry->u.symlink.sym_offset + fsentry->u.symlink.sym_size + 1, image_align); // now we can set the size
  966.       fsentry->UNSAVED_was_data_written = true; // no data to save
  967.    }
  968.    else // necessarily a device node
  969.    {
  970.       fsentry->u.device.dev  = strtol (entry_parms->data.bytes, NULL, 0); // use strtol() to parse decimal (...), hexadecimal (0x...) and octal (0...) numbers
  971.       fsentry->u.device.rdev = strtol (strchr (entry_parms->data.bytes, ':') + 1, NULL, 0); // use strtol() to parse decimal (...), hexadecimal (0x...) and octal (0...) numbers
  972.       fsentry->u.device.path = strdup (stored_pathname[0] == '/' ? &stored_pathname[1] : stored_pathname);
  973.  
  974.       fsentry->header.size = (uint16_t) ROUND_TO_UPPER_MULTIPLE (sizeof (fsentry->header) + sizeof (uint32_t) + sizeof (uint32_t) + strlen (fsentry->u.device.path), image_align); // now we can set the size
  975.       fsentry->UNSAVED_was_data_written = true; // no data to save
  976.    }
  977.    (*fsentry_count)++;
  978.  
  979.    // should we also add a symlink to this entry ? (in case we stored a dylib file under its canonical name)
  980.    if (original_stored_pathname != NULL)
  981.    {
  982.       entry_parms->is_compiled_bootscript = false;
  983.       entry_parms->should_autosymlink_dylib = false;
  984.       entry_parms->should_follow_symlinks = false;
  985.       entry_parms->st_mode = S_IFLNK | 0777; // NOTE: mkifs stores symlink permissions as rwxrwxrwx !
  986.       entry_parms->extra_ino_flags = (fsentry->header.ino & (IFS_INO_PROCESSED_ELF | IFS_INO_RUNONCE_ELF | IFS_INO_BOOTSTRAP_EXE)); // preserve target's inode flags
  987.       last_dirsep = strrchr (stored_pathname, '/');
  988.       old_data = entry_parms->data.bytes; // backup previous data pointer
  989.       entry_parms->data.bytes = (uint8_t *) (last_dirsep == NULL ? stored_pathname : last_dirsep + 1); // store symlink target in dirent data
  990.       entry_parms->data.size = strlen (entry_parms->data.bytes);
  991.       add_fsentry (fsentries, fsentry_count, entry_parms, original_stored_pathname, NULL);
  992.       entry_parms->data.bytes = old_data; // restore previous data pointer so that it can be freed normally
  993.    }
  994.  
  995.    return (*fsentry_count);
  996. }
  997.  
  998.  
  999. static int fsentry_compare_pathnames_cb (const void *a, const void *b)
  1000. {
  1001.    // qsort() callback that compares two imagefs filesystem entries and sort them alphabetically by pathname
  1002.  
  1003.    const fsentry_t *entry_a = (const fsentry_t *) a;
  1004.    const fsentry_t *entry_b = (const fsentry_t *) b;
  1005.    const char *pathname_a = (S_ISDIR (entry_a->header.mode) ? entry_a->u.dir.path : (S_ISREG (entry_a->header.mode) ? entry_a->u.file.path : (S_ISLNK (entry_a->header.mode) ? entry_a->u.symlink.path : entry_a->u.device.path)));
  1006.    const char *pathname_b = (S_ISDIR (entry_b->header.mode) ? entry_b->u.dir.path : (S_ISREG (entry_b->header.mode) ? entry_b->u.file.path : (S_ISLNK (entry_b->header.mode) ? entry_b->u.symlink.path : entry_b->u.device.path)));
  1007.    return (strcmp (pathname_a, pathname_b));
  1008. }
  1009.  
  1010.  
  1011. static void update_MKIFS_PATH (const char *processor)
  1012. {
  1013.    // updates the value of MKIFS_PATH according to the passed processor name string, unless an environment variable already defines it
  1014.  
  1015.    char processor_base[16];
  1016.    size_t data_len;
  1017.    char *envvar;
  1018.    char *token;
  1019.    
  1020.    envvar = getenv ("MKIFS_PATH"); // look in the environment first, and construct a default one if not supplied
  1021.    if (envvar != NULL)
  1022.       MKIFS_PATH = envvar; // if envvar is present, set MKIFS_PATH to point to it
  1023.    else // envvar not present
  1024.    {
  1025.       if (MKIFS_PATH != NULL)
  1026.          free (MKIFS_PATH); // free any MKIFS_PATH that we constructed earlier
  1027.  
  1028.       strcpy_s (processor_base, sizeof (processor_base), processor); // construct PROCESSOR_BASE
  1029.       token = strchr (processor_base, '-');
  1030.       if (token != NULL)
  1031.          *token = 0; // split anything from the first dash onwards
  1032.       data_len = strlen (processor_base);
  1033.       if ((data_len > 2) && ((processor_base[data_len - 2] == 'b') || (processor_base[data_len - 2] == 'l')) && (processor_base[data_len - 1] == 'e'))
  1034.          processor_base[data_len - 2] = 0; // if it ends with "le" or "be", strip that too
  1035.  
  1036.       MKIFS_PATH = malloc (10 * MAXPATHLEN); // construct a default MKIFS_PATH now
  1037.       ASSERT_WITH_ERRNO (MKIFS_PATH);
  1038.       sprintf_s (MKIFS_PATH, 10 * MAXPATHLEN,
  1039.                  "." PATH_SEP "%s/%s/sbin" PATH_SEP "%s/%s/usr/sbin" PATH_SEP "%s/%s/boot/sys" PATH_SEP "%s/%s/boot/sys" PATH_SEP "%s/%s/bin" PATH_SEP "%s/%s/usr/bin" PATH_SEP "%s/%s/lib" PATH_SEP "%s/%s/lib/dll" PATH_SEP "%s/%s/usr/lib", // use a platform-specific character as path separator
  1040.                  QNX_TARGET, processor,
  1041.                  QNX_TARGET, processor,
  1042.                  QNX_TARGET, processor,
  1043.                  QNX_TARGET, processor_base,
  1044.                  QNX_TARGET, processor,
  1045.                  QNX_TARGET, processor,
  1046.                  QNX_TARGET, processor,
  1047.                  QNX_TARGET, processor,
  1048.                  QNX_TARGET, processor);
  1049.    }
  1050.  
  1051.    return;
  1052. }
  1053.  
  1054.  
  1055. int main (int argc, char **argv)
  1056. {
  1057.    // program entrypoint
  1058.  
  1059.    typedef struct ifs_offsets_s
  1060.    {
  1061.       size_t startupheader;
  1062.       size_t startuptrailer;
  1063.       size_t imageheader;
  1064.       size_t imagedir;
  1065.       size_t imagetrailer;
  1066.    } ifs_offsets_t;
  1067.    typedef struct ifs_s
  1068.    {
  1069.       buffer_t data;
  1070.       ifs_offsets_t offsets;
  1071.       size_t final_size; // final size: not known (because not set) until everything has been written
  1072.    } ifs_t;
  1073.  
  1074.    static startup_header_t startup_header = { 0 }; // output IFS's startup header
  1075.    static startup_trailer_v2_t startup_trailer = { 0 }; // output IFS's startup trailer (version 2, with SHA-512 checksum and int32 checksum)
  1076.    static image_header_t image_header = { 0 }; // output IFS's imagefs header
  1077.    static image_trailer_v2_t image_trailer = { 0 }; // output IFS's imagefs trailer (version 2, with SHA-512 checksum and int32 checksum)
  1078.    static fsentry_t *fsentries = NULL; // output IFS's filesystem entries
  1079.    static size_t fsentry_count = 0; // number of entries in the IFS filesystem
  1080.    static parms_t default_parms = { // default parameters for a filesystem entry
  1081.       .dperms = 0755,
  1082.       .perms = 0644,
  1083.       .uid = 0,
  1084.       .gid = 0,
  1085.       .st_mode = S_IFREG,
  1086.       .mtime = UINT32_MAX,
  1087.       .mtime_for_inline_files = UINT32_MAX,
  1088.       .prefix = "/proc/boot",
  1089.       .should_follow_symlinks = true, // [+|-followlink]
  1090.       .should_autosymlink_dylib = true, // [+|-autolink]
  1091.       .is_compiled_bootscript = false, // [+|-script]
  1092.       .extra_ino_flags = 0,
  1093.       .search = "",
  1094.       .data = { NULL, 0 }
  1095.    };
  1096.    static parms_t entry_parms = { 0 }; // current parameters for a filesystem entry (will be initialized to default_parms each time a new entry is parsed in the build file)
  1097.  
  1098.    char path_on_buildhost[MAXPATHLEN] = "";
  1099.    char path_in_ifs[MAXPATHLEN] = "";
  1100.    char *ifs_pathname = NULL;
  1101.    void *reallocated_ptr;
  1102.    struct tm utc_time;
  1103.    struct stat stat_buf;
  1104.    size_t imgdir_size;
  1105.    size_t available_space;
  1106.    size_t allocated_size;
  1107.    size_t fsentry_index;
  1108.    size_t largest_index;
  1109.    size_t largest_size;
  1110.    size_t curr_offset;
  1111.    ifs_t ifs = { 0 };
  1112.    int32_t checksum;
  1113.    char *first_pathname = NULL;
  1114.    char *second_pathname = NULL;
  1115.    char *specifiedpathname_start;
  1116.    char *directiveblock_start;
  1117.    char *write_ptr;
  1118.    char *line_ptr;
  1119.    char *token;
  1120.    char *value;
  1121.    char *sep;
  1122.    char *ctx;
  1123.    int arg_index;
  1124.    bool is_quoted_context = false;
  1125.    bool is_escaped_char = false;
  1126.    bool should_discard_inline_contents = false;
  1127.    bool want_info = false;
  1128.    bool want_everything = false;
  1129.    bool want_help = false;
  1130.    bool want_dump = false;
  1131.    bool want_strip = false;
  1132.    bool want_hexdump = false;
  1133.    bool is_foreign_endianness;
  1134.    int string_len;
  1135.    int read_char;
  1136.    FILE *buildfile_fp;
  1137.  
  1138.    // parse arguments
  1139.    for (arg_index = 1; arg_index < argc; arg_index++)
  1140.    {
  1141.       if ((strcmp (argv[arg_index], "--bootfile") == 0) && (arg_index + 1 < argc)) // --bootfile path/to/blob.bin
  1142.          bootfile_pathname = argv[++arg_index];
  1143.       else if ((strcmp (argv[arg_index], "--startupfile") == 0) && (arg_index + 1 < argc)) // --startupfile path/to/blob.bin@0x1030
  1144.       {
  1145.          sep = strchr (argv[++arg_index], '@');
  1146.          if ((sep == NULL) || (sep[1] == 0))
  1147.             DIE_WITH_EXITCODE (1, "the --startupfile arguments expects <pathname>@<entrypoint_from_image_base>");
  1148.          *sep = 0;
  1149.          startupfile_pathname = argv[arg_index];
  1150.          startupfile_ep_from_imagebase = (size_t) read_integer (sep + 1);
  1151.       }
  1152.       else if ((strcmp (argv[arg_index], "--kernelfile") == 0) && (arg_index + 1 < argc)) // --kernelfile path/to/blob.bin@0x32000
  1153.       {
  1154.          sep = strchr (argv[++arg_index], '@');
  1155.          if ((sep == NULL) || (sep[1] == 0))
  1156.             DIE_WITH_EXITCODE (1, "the --kernelfile arguments expects <pathname>@<fileoffset>");
  1157.          *sep = 0;
  1158.          kernelfile_pathname = argv[arg_index];
  1159.          kernelfile_offset = (size_t) read_integer (sep + 1);
  1160.       }
  1161.       else if (strcmp (argv[arg_index], "-n") == 0)
  1162.          default_parms.mtime_for_inline_files = 0; // inline files should have a mtime set to zero
  1163.       else if (strcmp (argv[arg_index], "-nn") == 0)
  1164.       {
  1165.          default_parms.mtime = 0; // *all* files should have a mtime set to zero
  1166.          default_parms.mtime_for_inline_files = 0;
  1167.       }
  1168.       else if ((strcmp (argv[arg_index], "--outdir") == 0) && (arg_index + 1 < argc)) // --outdir path
  1169.          second_pathname = argv[++arg_index];
  1170.       else if ((strcmp (argv[arg_index], "--outfile") == 0) && (arg_index + 1 < argc)) // --outfile pathname
  1171.          second_pathname = argv[++arg_index];
  1172.       else if (strcmp (argv[arg_index], "--info") == 0)
  1173.          want_info = true;
  1174.       else if (strcmp (argv[arg_index], "--dump") == 0)
  1175.          want_dump = true;
  1176.       else if (strcmp (argv[arg_index], "--hexdump") == 0) // voluntarily undocumented
  1177.          want_hexdump = true;
  1178.       else if (strcmp (argv[arg_index], "--strip") == 0)
  1179.          want_strip = true;
  1180.       else if (strcmp (argv[arg_index], "--everything") == 0)
  1181.          want_everything = true;
  1182.       else if (strncmp (argv[arg_index], "-v", 2) == 0) // -v[....]
  1183.          verbose_level += (int) strlen (argv[arg_index] + 1); // increase verbosity by the number of characters in this flag
  1184.       else if ((strcmp (argv[arg_index], "-?") == 0) || (strcmp (argv[arg_index], "--help") == 0))
  1185.          want_help = true;
  1186.       else if (first_pathname == NULL)
  1187.          first_pathname = argv[arg_index];
  1188.       else if (second_pathname == NULL)
  1189.          second_pathname = argv[arg_index];
  1190.       else
  1191.          DIE_WITH_EXITCODE (1, "unrecognized option: '%s'", argv[arg_index]);
  1192.    }
  1193.  
  1194.    // do we not have enough information to run ?
  1195.    if (want_help || (first_pathname == NULL) || (!want_info && !want_dump && !want_hexdump && !want_strip && (second_pathname == NULL)))
  1196.    {
  1197.       FILE *out = (want_help ? stdout : stderr); // select the right output channel
  1198.       fprintf (out, "ifstool - QNX in-kernel filesystem creation utility by Pierre-Marie Baty <pm@pmbaty.com>\n");
  1199.       fprintf (out, "          version " VERSION_FMT_YYYYMMDD "\n", VERSION_ARG_YYYYMMDD);
  1200.       if (!want_help)
  1201.          fprintf (out, "error: missing parameters\n");
  1202.       fprintf (out, "usage:\n");
  1203.       fprintf (out, "    ifstool [--bootfile <pathname>] [--startupfile <pathname>@<EP_from_imgbase>] [--kernelfile <pathname>@<fileoffs>] [-n[n]] [-v[...]] <buildfile> <outfile>\n");
  1204.       fprintf (out, "    ifstool --info [--everything] <ifs file>\n");
  1205.       fprintf (out, "    ifstool --dump [--outdir <path>] <ifs file>\n");
  1206.       fprintf (out, "    ifstool --strip [--outfile <pathname>] <ELF file>\n");
  1207.       fprintf (out, "    ifstool --help\n");
  1208.       fprintf (out, "NOTE: the compilation feature requires predigested boot, startup and kernel files produced by mkifs.\n");
  1209.       exit (want_help ? 0 : 1);
  1210.    }
  1211.  
  1212.    // do we want info about a particular IFS ? if so, dissecate it
  1213.    if (want_info)
  1214.       exit (dump_ifs_info (first_pathname, want_everything));
  1215.  
  1216.    // else do we want to dump its contents ? if so, do so
  1217.    else if (want_dump)
  1218.       exit (dump_ifs_contents (first_pathname, (second_pathname != NULL ? second_pathname : ".")));
  1219.  
  1220.    // else do we want to hex dump a file ? (this is voluntarily undocumented)
  1221.    else if (want_hexdump)
  1222.       exit (dump_file_hex (first_pathname));
  1223.  
  1224.    // else do we want to strip an ELF file ? if so, do so
  1225.    else if (want_strip)
  1226.    {
  1227.       buffer_t file;
  1228.       ASSERT (Buffer_ReadFromFile (&file, first_pathname), "can't open \"%s\" for reading: %s", first_pathname, strerror (errno));
  1229.       ASSERT (Buffer_StripELFFile (&file, first_pathname), "error stripping \"%s\": %s", first_pathname, strerror (errno));
  1230.       ASSERT_WITH_ERRNO (Buffer_WriteToFile (&file, (second_pathname != NULL ? second_pathname : "<stdout>")));
  1231.       exit (0);
  1232.    }
  1233.  
  1234.    // we want to CREATE an IFS file
  1235.    buildfile_pathname = first_pathname; // assign the pathnames properly
  1236.    ifs_pathname = second_pathname;
  1237.  
  1238.    // make sure we have ${QNX_TARGET} pointing somewhere
  1239.    QNX_TARGET = getenv ("QNX_TARGET");
  1240.    if (QNX_TARGET == NULL)
  1241.       DIE_WITH_EXITCODE (1, "the QNX_TARGET environment variable is not set");
  1242.    else if (access (QNX_TARGET, 0) != 0)
  1243.       DIE_WITH_EXITCODE (1, "the QNX_TARGET environment variable doesn't point to an existing directory");
  1244.  
  1245.    // prepare a default MKIFS_PATH assuming the host processor
  1246.    update_MKIFS_PATH (image_processor);
  1247.  
  1248.    // open build file
  1249.    fopen_s (&buildfile_fp, buildfile_pathname, "rb");
  1250.    if (buildfile_fp == NULL)
  1251.       DIE_WITH_EXITCODE (1, "unable to open build file \"%s\" for reading: %s", buildfile_pathname, strerror (errno));
  1252.  
  1253.    // stack up filesystem entries
  1254.    memcpy (&entry_parms, &default_parms, sizeof (default_parms));
  1255.    entry_parms.st_mode = S_IFDIR | default_parms.dperms;
  1256.    add_fsentry (&fsentries, &fsentry_count, &entry_parms, "", NULL); // add the root dir first
  1257.  
  1258.    // parse the IFS build file line per line
  1259.    while (fgets (line_buffer, sizeof (line_buffer), buildfile_fp) != NULL)
  1260.    {
  1261.       if (current_line != NULL)
  1262.          free (current_line);
  1263.       current_line = strdup (line_buffer);
  1264.       ASSERT_WITH_ERRNO (current_line);
  1265.       lineno++; // keep track of current line number
  1266.  
  1267.       line_ptr = line_buffer;
  1268.       while ((*line_ptr != 0) && isspace (*line_ptr))
  1269.          line_ptr++; // skip leading spaces
  1270.  
  1271.       if ((*line_ptr == 0) || (*line_ptr == '#'))
  1272.          continue; // skip empty or comment lines
  1273.  
  1274.       string_len = (int) strlen (line_buffer);
  1275.       if ((string_len > 0) && (line_buffer[string_len - 1] == '\n'))
  1276.          line_buffer[string_len - 1] = 0; // chop off newline for easier debug output
  1277.  
  1278.       // reset entry values
  1279.       memcpy (&entry_parms, &default_parms, sizeof (default_parms));
  1280.       path_in_ifs[0] = 0;
  1281.       path_on_buildhost[0] = 0;
  1282.       should_discard_inline_contents = false;
  1283.  
  1284.       // does this line start with an attribute block ?
  1285.       if (*line_ptr == '[')
  1286.       {
  1287.          line_ptr++; // skip the leading square bracket
  1288.          directiveblock_start = line_ptr; // remember where it starts
  1289.          is_quoted_context = false;
  1290.          while ((*line_ptr != 0) && !((*line_ptr == ']') && (line_ptr[-1] != '\\') && !is_quoted_context))
  1291.          {
  1292.             if (*line_ptr == '"')
  1293.                is_quoted_context ^= true; // remember when we're between quotes
  1294.             else if (!is_quoted_context && (*line_ptr == ' '))
  1295.                *line_ptr = RECORD_SEP[0]; // turn all spaces outside quoted contexts into an ASCII record separator to ease token splitting
  1296.             line_ptr++; // reach the next unescaped closing square bracket
  1297.          }
  1298.          if (*line_ptr != ']')
  1299.          {
  1300.             LOG ("warning", 0, "syntax error in \"%s\" line %d: unterminated attributes block (skipping)", buildfile_pathname, lineno);
  1301.             continue; // invalid attribute block, skip line
  1302.          }
  1303.          *line_ptr = 0; // end the attribute block so that it is a parsable C string
  1304.  
  1305.          // now parse the attribute tokens
  1306.          // DOCUMENTATION: https://www.qnx.com/developers/docs/8.0/com.qnx.doc.neutrino.utilities/topic/m/mkifs.html#mkifs__description
  1307.          token = strtok_r (directiveblock_start, RECORD_SEP, &ctx);
  1308.          while (token != NULL)
  1309.          {
  1310.             // evaluate attribute token
  1311.             #define REACH_TOKEN_VALUE() do { value = strchr (token, '=') + 1; if (*value == '"') value++; } while (0)
  1312.             if      (strncmp (token, "uid=",     4) == 0) { REACH_TOKEN_VALUE (); entry_parms.uid     = (int) read_integer (value); }
  1313.             else if (strncmp (token, "gid=",     4) == 0) { REACH_TOKEN_VALUE (); entry_parms.gid     = (int) read_integer (value); }
  1314.             else if (strncmp (token, "dperms=",  7) == 0) { REACH_TOKEN_VALUE (); entry_parms.dperms  = (int) read_integer (value); }
  1315.             else if (strncmp (token, "perms=",   6) == 0) { REACH_TOKEN_VALUE (); entry_parms.perms   = (int) read_integer (value); }
  1316.             else if (strncmp (token, "type=",    5) == 0) { REACH_TOKEN_VALUE ();
  1317.                if      (strcmp (value, "dir")  == 0) entry_parms.st_mode = S_IFDIR;
  1318.                else if (strcmp (value, "file") == 0) entry_parms.st_mode = S_IFREG;
  1319.                else if (strcmp (value, "link") == 0) entry_parms.st_mode = S_IFLNK;
  1320.                else if (strcmp (value, "fifo") == 0) entry_parms.st_mode = S_IFIFO;
  1321.                else DIE_WITH_EXITCODE (1, "invalid 'type' attribute in \"%s\" line %d: '%s'", buildfile_pathname, lineno, value);
  1322.             }
  1323.             else if (strncmp (token, "prefix=",  7) == 0) { REACH_TOKEN_VALUE (); strcpy_s (entry_parms.prefix, sizeof (entry_parms.prefix), (*value == '/' ? value + 1 : value)); } // skip possible leading slash in prefix
  1324.             else if (strncmp (token, "image=",   6) == 0) { REACH_TOKEN_VALUE ();
  1325.                image_base = (uint32_t) read_integer (value); // read image base address
  1326.                if ((sep = strchr (value, '-')) != NULL) image_end       = (uint32_t) read_integer (sep + 1); // if we have a dash, read optional image end (TODO: check this value and produce an error in the relevant case. Not important.)
  1327.                if ((sep = strchr (value, ',')) != NULL) image_maxsize   = (uint32_t) read_integer (sep + 1); // if we have a comma, read optional image max size
  1328.                if ((sep = strchr (value, '=')) != NULL) image_totalsize = (uint32_t) read_integer (sep + 1); // if we have an equal sign, read optional image padding size
  1329.                if ((sep = strchr (value, '%')) != NULL) image_align     = (uint32_t) read_integer (sep + 1); // if we have a modulo sign, read optional image aligmnent
  1330.                LOG_INFO ("image 0x%x-0x%x maxsize %d totalsize %d align %d", image_base, image_end, image_maxsize, image_totalsize, image_align);
  1331.             }
  1332.             else if (strncmp (token, "virtual=", 8) == 0) { REACH_TOKEN_VALUE ();
  1333.                if ((bootfile_pathname == NULL) || (startupfile_pathname == NULL) || (kernelfile_pathname == NULL)) // HACK until I figure out how to re-create them
  1334.                   DIE_WITH_EXITCODE (1, "creating bootable images require the --bootfile, --startupfile and --kernelfile command-line options in \"%s\" line %d", buildfile_pathname, lineno);
  1335.                if ((sep = strchr (value, ',')) != NULL) // do we have a comma separating (optional) processor and boot file name ?
  1336.                {
  1337.                   *sep = 0;
  1338.                   strcpy_s (image_processor, sizeof (image_processor), value); // save processor
  1339.                   update_MKIFS_PATH (image_processor);
  1340.                   value = sep + 1;
  1341.                }
  1342.                //sprintf (image_bootfile, "%s/%s/boot/sys/%s.boot", QNX_TARGET, image_processor, value); // save preboot file name (TODO: we should search in MKIFS_PATH instead of this. Not important.)
  1343.                //strcpy (image_bootfile, bootfile_pathname); // FIXME: HACK
  1344.                if (stat (bootfile_pathname, &stat_buf) != 0)
  1345.                   DIE_WITH_EXITCODE (1, "unable to stat the boot file \"%s\" specified in \"%s\" line %d: %s", bootfile_pathname, buildfile_pathname, lineno, strerror (errno));
  1346.                bootfile_size = stat_buf.st_size; // save preboot file size
  1347.                LOG_INFO ("processor \"%s\" bootfile \"%s\"\n", image_processor, bootfile_pathname);
  1348. #if 1
  1349.                // ######################################################################################################################################################################################################################################
  1350.                // # FIXME: figure out how to re-create it: linker call involved
  1351.                // # $ x86_64-pc-nto-qnx8.0.0-ld --sysroot=${QNX_TARGET}/x86_64/ -T${QNX_TARGET}/x86_64/lib/nto.link --section-start .text=0xffff800000001000 --no-relax ${QNX_TARGET}/x86_64/boot/sys/procnto-smp-instr -o procnto-smp-instr.sym.UNSTRIPPED
  1352.                // ######################################################################################################################################################################################################################################
  1353. //               if (!Buffer_ReadFromFile (&entry_parms.data, kernelfile_pathname))
  1354. //                  DIE_WITH_EXITCODE (1, "unable to read precompiled kernel file \"%s\" specified in --kernelfile argument: %s", kernelfile_pathname, strerror (errno));
  1355. #else // nonworking
  1356.                strcpy (path_on_buildhost, "procnto-smp-instr");
  1357. #endif // nonworking
  1358.             }
  1359.             else if (strncmp (token, "mtime=", 6) == 0) { REACH_TOKEN_VALUE (); if (strcmp (value, "*") == 0) entry_parms.mtime = UINT32_MAX; else {
  1360.                   // value *must* be "YYYY-MM-DD-HH:MM:SS" by specification
  1361.                   memset (&utc_time, 0, sizeof (utc_time));
  1362.                   if (sscanf_s (value, "%u-%u-%u-%u:%u:%u", &utc_time.tm_year, &utc_time.tm_mon, &utc_time.tm_mday, &utc_time.tm_hour, &utc_time.tm_min, &utc_time.tm_sec) != 6)
  1363.                   {
  1364.                      LOG_WARNING ("syntax error in \"%s\" line %d: mtime specification not in YYYY-MM-DD-HH:MM:SS format (skipping)", buildfile_pathname, lineno);
  1365.                      continue; // invalid attribute block, skip line
  1366.                   }
  1367.                   utc_time.tm_mon--; // convert month from [1-12] to [0-11]
  1368.                   entry_parms.mtime = (uint32_t) mktime (&utc_time);
  1369.                }
  1370.             }
  1371.             else if (strcmp (token, "+script")     == 0) {
  1372.                entry_parms.is_compiled_bootscript = true;
  1373.                ASSERT_WITH_ERRNO (Buffer_InitWithByteArray (&entry_parms.data, INITIAL_STARTUP_SCRIPT)); // FIXME: HACK until the script compiler is implemented
  1374.                should_discard_inline_contents = true; // remember we already have data (so as to discard the inline block's contents)
  1375.             }
  1376.             else if (strcmp (token, "-script")     == 0) entry_parms.is_compiled_bootscript = false;
  1377.             else if (strcmp (token, "+followlink") == 0) entry_parms.should_follow_symlinks = true;
  1378.             else if (strcmp (token, "-followlink") == 0) entry_parms.should_follow_symlinks = false;
  1379.             else if (strcmp (token, "+autolink")   == 0) entry_parms.should_autosymlink_dylib = true;
  1380.             else if (strcmp (token, "-autolink")   == 0) entry_parms.should_autosymlink_dylib = false;
  1381.             else if (strcmp (token, "+keeplinked") == 0) entry_parms.should_keep_ld_output = true;
  1382.             else if (strcmp (token, "-keeplinked") == 0) entry_parms.should_keep_ld_output = false;
  1383.             else LOG_WARNING ("unimplemented attribute in \"%s\" line %d: '%s'", buildfile_pathname, lineno, token);
  1384.             #undef REACH_TOKEN_VALUE
  1385.  
  1386.             token = strtok_r (NULL, RECORD_SEP, &ctx); // proceed to next attribute token
  1387.          }
  1388.  
  1389.          line_ptr++; // reach the next character
  1390.          while ((*line_ptr != 0) && isspace (*line_ptr))
  1391.             line_ptr++; // skip leading spaces
  1392.  
  1393.          // are we at the end of the line ? if so, it means the attribute values that are set should become the default
  1394.          if ((*line_ptr == 0) || (*line_ptr == '#'))
  1395.          {
  1396.             #define APPLY_DEFAULT_ATTR_NUM(attr,descr,fmt) do { if (entry_parms.attr != default_parms.attr) { \
  1397.                   LOG_INFO ("changing default " descr " from " fmt " to " fmt " by attribute at \"%s\" line %d", default_parms.attr, entry_parms.attr, buildfile_pathname, lineno); \
  1398.                   default_parms.attr = entry_parms.attr; \
  1399.                } } while (0)
  1400.             #define APPLY_DEFAULT_ATTR_STR(attr,descr,fmt) do { if (strcmp (entry_parms.attr, default_parms.attr) != 0) { \
  1401.                   LOG_INFO ("changing default " descr " from " fmt " to " fmt " by attribute at \"%s\" line %d", default_parms.attr, entry_parms.attr, buildfile_pathname, lineno); \
  1402.                   strcpy_s (default_parms.attr, sizeof (default_parms.attr), entry_parms.attr); \
  1403.                } } while (0)
  1404.             APPLY_DEFAULT_ATTR_NUM (dperms,                   "directory permissions",           "0%o");
  1405.             APPLY_DEFAULT_ATTR_NUM (perms,                    "file permissions",                "0%o");
  1406.             APPLY_DEFAULT_ATTR_NUM (uid,                      "owner ID",                        "%d");
  1407.             APPLY_DEFAULT_ATTR_NUM (gid,                      "group ID",                        "%d");
  1408.             APPLY_DEFAULT_ATTR_NUM (st_mode,                  "inode type",                      "0%o");
  1409.             APPLY_DEFAULT_ATTR_STR (prefix,                   "prefix",                          "\"%s\"");
  1410.             APPLY_DEFAULT_ATTR_NUM (is_compiled_bootscript,   "compiled script state",           "%d");
  1411.             APPLY_DEFAULT_ATTR_NUM (should_follow_symlinks,   "symlink resolution",              "%d");
  1412.             APPLY_DEFAULT_ATTR_NUM (should_autosymlink_dylib, "dylib canonical name symlinking", "%d");
  1413.             APPLY_DEFAULT_ATTR_NUM (should_keep_ld_output,    "linker output preservation",      "%d");
  1414.             #undef APPLY_DEFAULT_ATTR_STR
  1415.             #undef APPLY_DEFAULT_ATTR_NUM
  1416.             continue; // end of line reached, proceed to the next line
  1417.          }
  1418.          // end of attributes parsing
  1419.       } // end of "this line starts with an attributes block"
  1420.  
  1421.       // there's data in this line. We expect a filename in the IFS. Read it and unescape escaped characters
  1422.       string_len = sprintf_s (path_in_ifs, sizeof (path_in_ifs), "%s", entry_parms.prefix);
  1423.       while ((string_len > 0) && (path_in_ifs[string_len - 1] == '/'))
  1424.          string_len--; // chop off any trailing slashes from prefix
  1425.       write_ptr = &path_in_ifs[string_len];
  1426.       *write_ptr++ = '/'; // add ONE trailing slash
  1427.       specifiedpathname_start = write_ptr; // remember the specified pathname will start here
  1428.       is_quoted_context = (*line_ptr == '"');
  1429.       if (is_quoted_context)
  1430.          line_ptr++; // skip a possible initial quote
  1431.       if (*line_ptr == '/')
  1432.       {
  1433.          LOG_WARNING ("paths in the IFS file should not begin with a leading '/' in \"%s\" line %d", buildfile_pathname, lineno);
  1434.          line_ptr++; // consistency check: paths in the IFS should not begin with a '/'
  1435.       }
  1436.       while ((*line_ptr != 0) && ((!is_quoted_context && (*line_ptr != '=') && !isspace (*line_ptr)) || (is_quoted_context && (*line_ptr == '"'))))
  1437.       {
  1438.          if (*line_ptr == '\\')
  1439.          {
  1440.             line_ptr++;
  1441.             *write_ptr++ = *line_ptr; // unescape characters that are escaped with '\'
  1442.          }
  1443.          else
  1444.             *write_ptr++ = *line_ptr;
  1445.          line_ptr++;
  1446.       }
  1447.       *write_ptr = 0; // terminate the string
  1448.       if (is_quoted_context && (*line_ptr == '"'))
  1449.          line_ptr++; // skip a possible final quote
  1450.  
  1451.       // we reached a space OR an equal sign
  1452.       while ((*line_ptr != 0) && isspace (*line_ptr))
  1453.          line_ptr++; // skip optional spaces after the filename in the IFS
  1454.  
  1455.       // do we have an equal sign ?
  1456.       if (*line_ptr == '=') // we must be creating either a directory or a file, do we have an equal sign ?
  1457.       {
  1458.          line_ptr++; // skip the equal sign
  1459.          while ((*line_ptr != 0) && isspace (*line_ptr))
  1460.             line_ptr++; // skip optional spaces after the equal sign
  1461.  
  1462.          if (*line_ptr == 0)
  1463.          {
  1464.             LOG_WARNING ("syntax error in \"%s\" line %d: missing data specification after equal sign (skipping)", buildfile_pathname, lineno);
  1465.             continue; // invalid symlink specification, skip line
  1466.          }
  1467.  
  1468.          // read the host system's path, it may be either a path or a contents definition. Is it a content definition ?
  1469.          if (*line_ptr == '{')
  1470.          {
  1471.             allocated_size = 0;
  1472.  
  1473.             line_ptr++; // skip the leading content definition
  1474.             is_escaped_char = false;
  1475.             for (;;)
  1476.             {
  1477.                read_char = fgetc (buildfile_fp);
  1478.                if (read_char == EOF)
  1479.                   DIE_WITH_EXITCODE (1, "syntax error in \"%s\" line %d: unterminated contents block (end of file reached)", buildfile_pathname, lineno); // invalid contents block
  1480.                else if ((read_char == '\\') && !is_escaped_char)
  1481.                   is_escaped_char = true; // remember the next char is escaped
  1482.                else if ((read_char == '}') && !is_escaped_char)
  1483.                   break; // found an unescaped closing bracked, stop parsing
  1484.                else
  1485.                {
  1486.                   is_escaped_char = false; // any other char, meaning the next one will not be escaped
  1487.                   if (!should_discard_inline_contents) // only store the contents if we do NOT know the data yet
  1488.                   {
  1489.                      if (entry_parms.data.size == allocated_size) // reallocate in 4 kb blocks
  1490.                      {
  1491.                         reallocated_ptr = realloc (entry_parms.data.bytes, allocated_size + 4096);
  1492.                         ASSERT_WITH_ERRNO (reallocated_ptr);
  1493.                         entry_parms.data.bytes = reallocated_ptr;
  1494.                         allocated_size += 4096;
  1495.                      }
  1496.                      entry_parms.data.bytes[entry_parms.data.size++] = read_char;
  1497.                   }
  1498.                   if (read_char == '\n')
  1499.                      lineno++; // update line counter as we parse the inline content
  1500.                }
  1501.             } // end for
  1502.          }
  1503.          else // not a content definition between { brackets }, must be either a pathname on the build host, or the target of a symlink
  1504.          {
  1505.             is_quoted_context = (*line_ptr == '"');
  1506.             if (is_quoted_context)
  1507.                line_ptr++; // skip a possible initial quote
  1508.             specifiedpathname_start = line_ptr; // remember where the specified pathname starts
  1509.             write_ptr = line_ptr; // now unescape all characters
  1510.             while ((*line_ptr != 0) && ((!is_quoted_context && !isspace (*line_ptr)) || (is_quoted_context && (*line_ptr == '"'))))
  1511.             {
  1512.                if (*line_ptr == '\\')
  1513.                {
  1514.                   line_ptr++;
  1515.                   *write_ptr++ = *line_ptr; // unescape characters that are escaped with '\'
  1516.                }
  1517.                else
  1518.                   *write_ptr++ = *line_ptr;
  1519.                line_ptr++;
  1520.             }
  1521.             *write_ptr = 0; // terminate the string
  1522.             if (is_quoted_context && (*line_ptr == '"'))
  1523.                line_ptr++; // skip a possible final quote
  1524.  
  1525.             if (S_ISLNK (entry_parms.st_mode)) // are we storing a symlink ?
  1526.                ASSERT_WITH_ERRNO (Buffer_InitWithCString (&entry_parms.data, specifiedpathname_start)); // if so, store the symlink target as the dirent's blob data
  1527.             else // it's a build host filesystem path
  1528.                strcpy_s (path_on_buildhost, sizeof (path_on_buildhost), specifiedpathname_start); // the path on the build host is given after the equal sign
  1529.          }
  1530.       }
  1531.       else // no equal sign, meaning the file will have the same name on the build host filesystem
  1532.       {
  1533.          // consistency check: symlinks MUST have an equal sign
  1534.          if (entry_parms.st_mode == S_IFLNK)
  1535.          {
  1536.             LOG_WARNING ("syntax error in \"%s\" line %d: missing equal sign and symlink target (skipping)", buildfile_pathname, lineno);
  1537.             continue; // invalid symlink specification, skip line
  1538.          }
  1539.  
  1540.          strcpy_s (path_on_buildhost, sizeof (path_on_buildhost), specifiedpathname_start); // the path on the build host is the one specified
  1541.          sep = strrchr (specifiedpathname_start, '/');
  1542.          if (sep != NULL)
  1543.             memmove (specifiedpathname_start, sep + 1, strlen (sep + 1) + 1); // the path in the IFS will be the BASENAME of the path specified (after the prefix)
  1544.       }
  1545.  
  1546.       // now add this entry to the image filesystem
  1547.       if (S_ISDIR (entry_parms.st_mode))
  1548.          entry_parms.st_mode |= entry_parms.dperms;
  1549.       else if (S_ISLNK (entry_parms.st_mode))
  1550.          entry_parms.st_mode |= 0777; // NOTE: mkifs sets symlink permissions to rwxrwxrwx !?
  1551.       else // file or device node
  1552.          entry_parms.st_mode |= entry_parms.perms;
  1553.  
  1554.       add_fsentry (&fsentries, &fsentry_count, &entry_parms, path_in_ifs, path_on_buildhost); // and add filesystem entry
  1555.  
  1556.       if (entry_parms.data.bytes != NULL)
  1557.          free (entry_parms.data.bytes); // if blob data was allocated, free it
  1558.    }
  1559.  
  1560.    fclose (buildfile_fp); // finished parsing the build file
  1561.  
  1562.    //////////////////////////////////
  1563.    // start constructing the IFS file
  1564.  
  1565.    Buffer_Initialize (&ifs.data);
  1566.  
  1567.    // do we have a startup file ? if so, this is a bootable image
  1568.    if (startupfile_pathname != NULL)
  1569.    {
  1570.       // write boot prefix
  1571.       // ######################################################################################################################################################################################################################################
  1572.       // # FIXME: figure out how to re-create it
  1573.       // ######################################################################################################################################################################################################################################
  1574.       buffer_t file;
  1575.       if (!Buffer_ReadFromFile (&file, bootfile_pathname))
  1576.          DIE_WITH_EXITCODE (1, "failed to open \"%s\" for reading: %s", bootfile_pathname, strerror (errno));
  1577.       ASSERT_WITH_ERRNO (Buffer_AppendBuffer (&ifs.data, &file)); // write boot blob
  1578.       Buffer_Forget (&file);
  1579.       ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (&ifs.data, ROUND_TO_UPPER_MULTIPLE (ifs.data.size, image_align))); // pad as necessary
  1580.  
  1581.       ifs.offsets.startupheader = ifs.data.size; // save startup header offset for future use
  1582.       memset (&startup_header, 0, sizeof (startup_header)); // prepare startup header
  1583.       memcpy (startup_header.signature, "\xeb\x7e\xff\x00", 4); // startup header signature, i.e. 0xff7eeb
  1584.       startup_header.version       = 1;
  1585.       startup_header.flags1        = STARTUP_HDR_FLAGS1_VIRTUAL | STARTUP_HDR_FLAGS1_TRAILER_V2; // flags, 0x21 (STARTUP_HDR_FLAGS1_VIRTUAL | STARTUP_HDR_FLAGS1_TRAILER_V2)
  1586.       startup_header.header_size   = sizeof (startup_header); // 256
  1587.       if (strcmp (image_processor, "x86_64") == 0)
  1588.          startup_header.machine = ELF_MACHINE_X86_64; // EM_X86_64
  1589.       else if (strcmp (image_processor, "aarch64le") == 0)
  1590.          startup_header.machine = ELF_MACHINE_AARCH64; // EM_AARCH64
  1591.       else
  1592.          DIE_WITH_EXITCODE (1, "unsupported processor type '%s' found in build file \"%s\"", image_processor, buildfile_pathname); // should not happen
  1593.       startup_header.startup_vaddr = image_base + (uint32_t) startupfile_ep_from_imagebase; // [I ] Virtual Address to transfer to after IPL is done, here 0x01403008 (appears in "Entry" column for "startup.*")
  1594.       startup_header.image_paddr   = image_base + (uint32_t) bootfile_size;                 // F[IS] Physical address of image, here 0x01400f30 (appears in "Offset" column for "startup-header" which is the first entry/start of file)
  1595.       startup_header.ram_paddr     = startup_header.image_paddr;                            // [IS] Physical address of RAM to copy image to (startup_size bytes copied), here 0x01400f30 (same as above)
  1596.       startup_header.ram_size      = WILL_BE_FILLED_LATER;                                  // [ S] Amount of RAM used by the startup program and executables contained in the file system, here 0x00cd6128 i.e. 13 459 752 dec. which is 13 Mb. i.e. IFS file size minus 0x9eee (40686)
  1597.       startup_header.startup_size  = WILL_BE_FILLED_LATER;                                  // [I ] Size of startup (never compressed), here 0x02f148 or 192 840 bytes
  1598.       startup_header.stored_size   = WILL_BE_FILLED_LATER;                                  // [I ] Size of entire image, here 0x00cd6128 (same as ram_size)
  1599.       startup_header.imagefs_size  = WILL_BE_FILLED_LATER;                                  // [ S] Size of uncompressed imagefs, here 0x00ca6fe0 or 13 266 912 bytes
  1600.       startup_header.preboot_size  = (uint16_t) bootfile_size;                              // [I ] Size of loaded before header, here 0xf30 or 3888 bytes (size of "bios.boot" file))
  1601.       ASSERT_WITH_ERRNO (Buffer_Append (&ifs.data, &startup_header, sizeof (startup_header))); // write startup header
  1602.       ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (&ifs.data, ROUND_TO_UPPER_MULTIPLE (ifs.data.size, image_align))); // pad as necessary
  1603.  
  1604.       // ######################################################################################################################################################################################################################################
  1605.       // # FIXME: figure out how to re-create it:
  1606.       // first: open "startup-x86" ELF file,
  1607.       //        lookup section headers table (there is no program headers table in this one)
  1608.       //        FIXME: figure out something in there where the result is 0x1401030 !!!
  1609.       // then: call the linker: ld --sysroot=${QNX_TARGET}/x86_64/ -T${QNX_TARGET}/x86_64/lib/nto.link --section-start .text=0x1401030 --no-relax ${QNX_TARGET}/x86_64/boot/sys/startup-x86 -o startup.bin.UNSTRIPPED
  1610.       // then: parse resulting ELF file, take all program segments and concatenate them --> this is the blob (FIXME: wrong?)
  1611.       // ######################################################################################################################################################################################################################################
  1612. #if 0 // nonworking
  1613.       // <deleted>
  1614. #else // working
  1615.       if (!Buffer_ReadFromFile (&file, startupfile_pathname))
  1616.          DIE_WITH_EXITCODE (1, "failed to open \"%s\" for reading: %s", startupfile_pathname, strerror (errno));
  1617.       ASSERT_WITH_ERRNO (Buffer_AppendBuffer (&ifs.data, &file)); // write startup blob
  1618.       Buffer_Forget (&file);
  1619. #endif // working
  1620.       ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (&ifs.data, ROUND_TO_UPPER_MULTIPLE (ifs.data.size, image_align))); // pad as necessary
  1621.  
  1622.       ifs.offsets.startuptrailer = ifs.data.size; // save startup trailer offset for future use
  1623.       ASSERT_WITH_ERRNO (Buffer_Append (&ifs.data, &startup_trailer, sizeof (startup_trailer))); // write startup trailer
  1624.       ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (&ifs.data, ROUND_TO_UPPER_MULTIPLE (ifs.data.size, image_align))); // pad as necessary
  1625.    }
  1626.  
  1627.    ifs.offsets.imageheader = ifs.data.size; // save image header offset for future use
  1628.    memset (&image_header, 0, sizeof (image_header)); // prepare image header
  1629.    memcpy (&image_header.signature, "imagefs", 7); // image filesystem signature, i.e. "imagefs"
  1630.    image_header.flags         = IMAGE_FLAGS_TRAILER_V2 | IMAGE_FLAGS_SORTED | IMAGE_FLAGS_INO_BITS; // endian neutral flags, 0x1c (IMAGE_FLAGS_TRAILER_V2 | IMAGE_FLAGS_SORTED | IMAGE_FLAGS_INO_BITS)
  1631.    image_header.image_size    = WILL_BE_FILLED_LATER; // size from header to end of trailer (here 0xca6fe0 or 13 266 912)
  1632.    image_header.hdr_dir_size  = WILL_BE_FILLED_LATER; // size from header to last dirent (here 0x12b8 or 4792)
  1633.    image_header.dir_offset    = sizeof (image_header); // offset from header to first dirent (here 0x5c or 92)
  1634.    image_header.boot_ino[0]   = image_kernel_ino; // inode of files for bootstrap p[ro?]g[ra?]ms (here 0xa0000002, 0, 0, 0)
  1635.    image_header.script_ino    = image_bootscript_ino; // inode of file for script (here 3)
  1636.    image_header.mountpoint[0] = '/'; // default mountpoint for image ("/" + "\0\0\0")
  1637.    ASSERT_WITH_ERRNO (Buffer_Append (&ifs.data, &image_header, sizeof (image_header))); // write image header
  1638.    ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (&ifs.data, ROUND_TO_UPPER_MULTIPLE (ifs.data.size, image_align))); // pad as necessary
  1639.  
  1640.    // write image directory (with the wrong file offsets)
  1641.    ifs.offsets.imagedir = ifs.data.size; // save image directory offset for future use
  1642.    curr_offset = ifs.offsets.imagedir;
  1643.    for (fsentry_index = 0; fsentry_index < fsentry_count; fsentry_index++)
  1644.    {
  1645.       Buffer_WriteIFSDirectoryEntryAt (&ifs.data, curr_offset, &fsentries[fsentry_index]); // write each dirent (the unknown fields will be fixed later)
  1646.       curr_offset += fsentries[fsentry_index].header.size; // advance to the next one
  1647.    }
  1648.    ASSERT_WITH_ERRNO (Buffer_AppendByteArray (&ifs.data, "\0\0\0\0")); // there seems to be 4 bytes of padding after the image directory
  1649.    imgdir_size = ifs.data.size - ifs.offsets.imagedir; // measure image dir size and save it for future use
  1650.  
  1651.    // is it a bootable image with a kernel file ?
  1652.    if ((startupfile_pathname != NULL) && (kernelfile_pathname != NULL))
  1653.    {
  1654.       // start by writing the startup script data blob, if we have one
  1655.       for (fsentry_index = 1; fsentry_index < fsentry_count; fsentry_index++)
  1656.          if (fsentries[fsentry_index].header.ino == image_bootscript_ino)
  1657.             break; // locate the startup script directory entry
  1658.       if (fsentry_index < fsentry_count) // found it ?
  1659.       {
  1660.          if (ifs.data.size + fsentries[fsentry_index].u.file.size >= kernelfile_offset)
  1661.             DIE_WITH_EXITCODE (1, "the compiled startup script is too big (%zd bytes, max is %zd) to fit at current offset %zd", (size_t) fsentries[fsentry_index].u.file.size, kernelfile_offset - ifs.data.size, ifs.data.size);
  1662.          fsentries[fsentry_index].u.file.offset = (uint32_t) (ifs.data.size - ifs.offsets.imageheader); // save file data blob offset in file structure
  1663.          Buffer_AppendIFSFileData (&ifs.data, &fsentries[fsentry_index]); // write file data
  1664.          fsentries[fsentry_index].UNSAVED_was_data_written = true; // and remember this file's data was written
  1665.       }
  1666.  
  1667.       // now write the filesystem entries that may fit before the kernel
  1668.       for (;;)
  1669.       {
  1670.          available_space = kernelfile_offset - ifs.data.size; // measure the available space until the kernel
  1671.  
  1672.          // look for the biggest one that can fit
  1673.          largest_index = 0;
  1674.          largest_size = 0;
  1675.          for (fsentry_index = 1; fsentry_index < fsentry_count; fsentry_index++)
  1676.          {
  1677.             if (!S_ISREG (fsentries[fsentry_index].header.mode) || fsentries[fsentry_index].UNSAVED_was_data_written || (fsentries[fsentry_index].u.file.size > available_space))
  1678.                continue; // skip all entries that don't have a separate data block, those who were written already and those that wouldn't fit
  1679.             if (fsentries[fsentry_index].u.file.size > largest_size)
  1680.             {
  1681.                largest_size = fsentries[fsentry_index].u.file.size;
  1682.                largest_index = fsentry_index;
  1683.             }
  1684.          }
  1685.          if (largest_size == 0)
  1686.             break; // found none ? if so, stop searching
  1687.          fsentry_index = largest_index;
  1688.  
  1689.          fsentries[fsentry_index].u.file.offset = (uint32_t) (ifs.data.size - ifs.offsets.imageheader); // save file data blob offset in file structure
  1690.          Buffer_AppendIFSFileData (&ifs.data, &fsentries[fsentry_index]); // write file data
  1691.          fsentries[fsentry_index].UNSAVED_was_data_written = true; // and remember this file's data was written
  1692.       }
  1693.       LOG_INFO ("Last written offset: 0x%zx", ifs.data.size);
  1694.       LOG_INFO ("Kernel file offset: 0x%zx", kernelfile_offset);
  1695.       ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (&ifs.data, kernelfile_offset)); // reach the kernel offset
  1696.  
  1697.       // now write the QNX kernel
  1698.       for (fsentry_index = 1; fsentry_index < fsentry_count; fsentry_index++)
  1699.          if (fsentries[fsentry_index].header.ino == image_kernel_ino)
  1700.             break; // locate the kernel directory entry (can't fail)
  1701.       fsentries[fsentry_index].u.file.offset = (uint32_t) (ifs.data.size - ifs.offsets.imageheader); // save file data blob offset in file structure
  1702. #ifdef PROCNTO_WIP
  1703.       // is the kernel we're storing a preprocessed ELF kernel ?
  1704.       if (fsentries[fsentry_index].header.ino & IFS_INO_PROCESSED_ELF)
  1705.       {
  1706.          elf = (elf_header_t *) fsentries[fsentry_index].u.file.UNSAVED_databuf; // quick access to ELF header
  1707.          table_count = ELF_GET_NUMERIC (elf, elf, program_header_table_len); // get the number of program headers
  1708.          for (table_index = 0; table_index < table_count; table_index++)
  1709.          {
  1710.             phdr = (elf_program_header_t *) &fsentries[fsentry_index].u.file.UNSAVED_databuf[ELF_GET_NUMERIC (elf, elf, program_header_table_offset) + (size_t) ELF_GET_NUMERIC (elf, elf, program_header_item_size) * table_index]; // quick access to program header
  1711.             corrective_offset = ELF_GET_NUMERIC (elf, phdr, virtual_addr) - ELF_GET_NUMERIC (elf, phdr, file_offset);
  1712.             if (ELF_GET_NUMERIC (elf, phdr, size_in_memory) != 0) // only patch the physical address of segments that have an actual size in memory
  1713.                ELF_SET_NUMERIC (elf, phdr, physical_addr, ELF_GET_NUMERIC (elf, phdr, physical_addr) + image_base + ifs.data.size - corrective_offset); // patch the physical address member of the program header table (NOTE: ifs.data.size is the location where the file data is about to be written)
  1714.          }
  1715.       }
  1716. #endif // PROCNTO_WIP
  1717.       Buffer_AppendIFSFileData (&ifs.data, &fsentries[fsentry_index]); // write kernel file data
  1718.       fsentries[fsentry_index].UNSAVED_was_data_written = true; // and remember this file's data was written
  1719.    }
  1720.  
  1721.    // then write all the other files by increasing inode number: ELF files first
  1722.    for (fsentry_index = 1; fsentry_index < fsentry_count; fsentry_index++)
  1723.    {
  1724.       if (!S_ISREG (fsentries[fsentry_index].header.mode) || fsentries[fsentry_index].UNSAVED_was_data_written // filter out anything that's not a file, and anything that's been already written
  1725.           || (fsentries[fsentry_index].u.file.size < 4) || (memcmp (fsentries[fsentry_index].u.file.UNSAVED_databuf, ELF_MAGIC_STR, 4) != 0)) // filter out anything that's not an ELF file
  1726.          continue; // skip all entries that don't have a separate data block and those who were written already
  1727.       fsentries[fsentry_index].u.file.offset = (uint32_t) (ifs.data.size - ifs.offsets.imageheader); // save file data blob offset in file structure
  1728.       Buffer_AppendIFSFileData (&ifs.data, &fsentries[fsentry_index]); // write file data
  1729.       fsentries[fsentry_index].UNSAVED_was_data_written = true; // and remember this file's data was written
  1730.    }
  1731.    for (fsentry_index = 1; fsentry_index < fsentry_count; fsentry_index++) // other files (non-ELF, e.g. scripts and data files) last
  1732.    {
  1733.       if (!S_ISREG (fsentries[fsentry_index].header.mode) || fsentries[fsentry_index].UNSAVED_was_data_written) // filter out anything that's not a file, and anything that's been already written
  1734.          continue; // skip all entries that don't have a separate data block and those who were written already
  1735.       fsentries[fsentry_index].u.file.offset = (uint32_t) (ifs.data.size - ifs.offsets.imageheader); // save file data blob offset in file structure
  1736.       Buffer_AppendIFSFileData (&ifs.data, &fsentries[fsentry_index]); // write file data
  1737.       fsentries[fsentry_index].UNSAVED_was_data_written = true; // and remember this file's data was written
  1738.    }
  1739.    ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (&ifs.data, ROUND_TO_UPPER_MULTIPLE (ifs.data.size, image_align))); // pad as necessary
  1740.  
  1741.    // finally, write trailer (including empty checksum)
  1742.    ifs.offsets.imagetrailer = ifs.data.size; // save image trailer offset for future use
  1743.    ASSERT_WITH_ERRNO (Buffer_Append (&ifs.data, &image_trailer, sizeof (image_trailer))); // write image trailer
  1744.    ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (&ifs.data, ROUND_TO_UPPER_MULTIPLE (ifs.data.size, image_align))); // pad as necessary
  1745.  
  1746.    // if we need to pad it to a specific length, do so
  1747.    ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (&ifs.data, image_totalsize));
  1748.    ifs.final_size = ifs.data.size; // and this is the final size of the IFS
  1749.  
  1750.    // see if we are past the image max size, in which case it's an error
  1751.    if (ifs.final_size > image_maxsize)
  1752.       DIE_WITH_EXITCODE (1, "image file \"%s\" size %zd exceeds max size (%zd)", ifs_pathname, ifs.final_size, (size_t) image_maxsize);
  1753.  
  1754.    // do we have a startup file ? if so, this is a bootable image
  1755.    if (startupfile_pathname != NULL)
  1756.    {
  1757.       // patch the startup header with its final values
  1758.       startup_header.startup_size = (uint32_t) (ifs.offsets.imageheader - ifs.offsets.startupheader); // size of startup header up to image header
  1759.       startup_header.imagefs_size = (uint32_t) (ifs.final_size - ifs.offsets.imageheader); // size of uncompressed imagefs
  1760.       startup_header.ram_size     = (uint32_t) (ifs.final_size - ifs.offsets.startupheader);
  1761.       startup_header.stored_size  = (uint32_t) (ifs.final_size - ifs.offsets.startupheader);
  1762.       ASSERT_WITH_ERRNO (Buffer_WriteAt (&ifs.data, ifs.offsets.startupheader, &startup_header, sizeof (startup_header))); // write the final startup header at its right offset
  1763.    }
  1764.  
  1765.    // rewrite image header with final values
  1766.    image_header.image_size = (uint32_t) (ifs.final_size - ifs.offsets.imageheader); // size of uncompressed imagefs
  1767.    image_header.hdr_dir_size = sizeof (image_header) + (uint32_t) imgdir_size; // size from start of image header to last dirent
  1768.    ASSERT_WITH_ERRNO (Buffer_WriteAt (&ifs.data, ifs.offsets.imageheader, &image_header, sizeof (image_header))); // write image header
  1769.  
  1770.    // rewrite image directory with final offset values
  1771.    if (image_header.flags & IMAGE_FLAGS_SORTED)
  1772.       qsort (&fsentries[1], fsentry_count - 1, sizeof (fsentry_t), fsentry_compare_pathnames_cb); // sort the filesystem entries by pathname if necessary
  1773.    curr_offset = ifs.offsets.imagedir; // position ourselves at the beginning of the image directory
  1774.    for (fsentry_index = 0; fsentry_index < fsentry_count; fsentry_index++)
  1775.    {
  1776.       Buffer_WriteIFSDirectoryEntryAt (&ifs.data, curr_offset, &fsentries[fsentry_index]); // rewrite each dirent
  1777.       curr_offset += fsentries[fsentry_index].header.size; // advance to the next one
  1778.    }
  1779.  
  1780.    // ALL CHECKSUMS AT THE VERY END
  1781.  
  1782.    // do we have a startup file ? if so, this is a bootable image
  1783.    if (startupfile_pathname != NULL)
  1784.    {
  1785.       // compute SHA-512 checksum and V1 checksum of startup block
  1786.       if (   ( (startup_header.flags1 & STARTUP_HDR_FLAGS1_BIGENDIAN) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
  1787.           || (!(startup_header.flags1 & STARTUP_HDR_FLAGS1_BIGENDIAN) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)))
  1788.          is_foreign_endianness = true; // if the header is big endian and we're on a little endian machine, or the other way around, it's a foreign endianness
  1789.       else
  1790.          is_foreign_endianness = false; // else this header is for the same endianness as us
  1791.  
  1792.       if (startup_header.flags1 & STARTUP_HDR_FLAGS1_TRAILER_V2) // is it a V2 trailer ?
  1793.       {
  1794.          SHA512 (&ifs.data.bytes[ifs.offsets.startupheader], ifs.offsets.startuptrailer - ifs.offsets.startupheader, &ifs.data.bytes[ifs.offsets.startuptrailer]); // compute SHA512 checksum and write it in place
  1795.          checksum = update_checksum (&ifs.data.bytes[ifs.offsets.startupheader], ifs.offsets.startuptrailer + SHA512_DIGEST_LENGTH - ifs.offsets.startupheader, is_foreign_endianness); // compute old checksum
  1796.          memcpy (&ifs.data.bytes[ifs.offsets.startuptrailer + SHA512_DIGEST_LENGTH], &checksum, 4); // and write it in place
  1797.       }
  1798.       else // old V1 trailer
  1799.       {
  1800.          checksum = update_checksum (&ifs.data.bytes[ifs.offsets.startupheader], ifs.offsets.startuptrailer - ifs.offsets.startupheader, is_foreign_endianness); // compute old checksum
  1801.          memcpy (&ifs.data.bytes[ifs.offsets.startuptrailer], &checksum, 4); // and write it in place
  1802.       }
  1803.    }
  1804.  
  1805.    // compute SHA-512 checksum and V1 checksum of image block
  1806.    if (   ( (image_header.flags & IMAGE_FLAGS_BIGENDIAN) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
  1807.        || (!(image_header.flags & IMAGE_FLAGS_BIGENDIAN) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)))
  1808.       is_foreign_endianness = true; // if the header is big endian and we're on a little endian machine, or the other way around, it's a foreign endianness
  1809.    else
  1810.       is_foreign_endianness = false; // else this header is for the same endianness as us
  1811.  
  1812.    if (image_header.flags & IMAGE_FLAGS_TRAILER_V2) // is it a V2 trailer ?
  1813.    {
  1814.       SHA512 (&ifs.data.bytes[ifs.offsets.imageheader], ifs.offsets.imagetrailer - ifs.offsets.imageheader, &ifs.data.bytes[ifs.offsets.imagetrailer]); // compute SHA512 checksum and write it in place
  1815.       checksum = update_checksum (&ifs.data.bytes[ifs.offsets.imageheader], ifs.offsets.imagetrailer + SHA512_DIGEST_LENGTH - ifs.offsets.imageheader, is_foreign_endianness); // compute old checksum
  1816.       memcpy (&ifs.data.bytes[ifs.offsets.imagetrailer + SHA512_DIGEST_LENGTH], &checksum, 4); // and write it in place
  1817.    }
  1818.    else // old V1 trailer
  1819.    {
  1820.       checksum = update_checksum (&ifs.data.bytes[ifs.offsets.imageheader], ifs.offsets.imagetrailer - ifs.offsets.imageheader, is_foreign_endianness); // compute old checksum
  1821.       memcpy (&ifs.data.bytes[ifs.offsets.imagetrailer], &checksum, 4); // and write it in place
  1822.    }
  1823.  
  1824.    // now rewrite IFS with the correct checksums
  1825.    ASSERT_WITH_ERRNO (Buffer_WriteToFile (&ifs.data, ifs_pathname));
  1826.  
  1827.    // finished, cleanup
  1828.    for (fsentry_index = 0; fsentry_index < fsentry_count; fsentry_index++)
  1829.    {
  1830.    }
  1831.  
  1832.    // and exit with a success code
  1833.    LOG_INFO ("Success");
  1834.    exit (0);
  1835. }
  1836.