Subversion Repositories QNX 8.QNX8 IFS tool

Rev

Rev 33 | Rev 35 | 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.  
  6. // standard C includes
  7. #include <stdint.h>
  8. #include <stdbool.h>
  9. #include <stdlib.h>
  10. #include <stdarg.h>
  11. #include <stdio.h>
  12. #include <string.h>
  13. #include <limits.h>
  14. #include <errno.h>
  15. #include <sys/stat.h>
  16. #include <ctype.h>
  17. #include <time.h>
  18.  
  19. // platform-specific includes
  20. #ifdef _MSC_VER
  21. #include <sys/utime.h>
  22. #include <process.h>
  23. #else // !_MSC_VER
  24. #include <sys/param.h>
  25. #include <sys/sysmacros.h>
  26. #include <sys/wait.h>
  27. #include <unistd.h>
  28. #include <dirent.h>
  29. #include <utime.h>
  30. #endif // _MSC_VER
  31.  
  32. // own includes
  33. #include "ucl/ucl.h"
  34. #include "minilzo.h"
  35. #include "buffer.h"
  36. #include "sha512.h"
  37. #include "elffile.h"
  38. #include "ifsfile.h"
  39. #include "utility.h"
  40.  
  41.  
  42. // compiler-specific glue
  43. #ifndef _MSC_VER
  44. #define sscanf_s sscanf // WARNING: TRUE FOR THIS FILE ONLY!
  45. #endif // !_MSC_VER
  46.  
  47.  
  48. // 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
  49. const char *__asan_default_options () { return ("detect_leaks=0"); }
  50.  
  51.  
  52. // placeholder value
  53. #define WILL_BE_FILLED_LATER 0xbaadf00d // urgh
  54.  
  55.  
  56. // boot type values
  57. #define BOOTTYPE_NONE 0
  58. #define BOOTTYPE_BIOS 1
  59. #define BOOTTYPE_UEFI 2
  60.  
  61.  
  62. // miscellaneous macros
  63. #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
  64. #ifdef _WIN32
  65. #define IS_DIRSEP(c) (((c) == '/') || ((c) == '\\')) // platform-specific directory separator, Win32 variant
  66. #define PATH_SEP ";" // platform-specific PATH element separator (as string), Win32 variant
  67. #else // !_WIN32, thus POSIX
  68. #define IS_DIRSEP(c) ((c) == '/') // platform-specific directory separator, UNIX variant
  69. #define PATH_SEP ":" // platform-specific PATH element separator (as string), UNIX variant
  70. #endif // _WIN32
  71. #define RECORD_SEP "\x1e" // arbitrarily-chosen ASCII record separator, as a C string suitable for e.g. strtok()
  72.  
  73.  
  74. // macros for constructing and destructing string arrays
  75. #define STRINGARRAY_INIT(string_array) do { (string_array)->args = NULL; (string_array)->count = 0; } while (0)
  76. #define STRINGARRAY_PUSH(string_array,str) do { \
  77.       reallocated_ptr = realloc ((string_array)->args, ((string_array)->count + 1) * sizeof (char *)); \
  78.       ASSERT_WITH_ERRNO (reallocated_ptr); \
  79.       (string_array)->args = reallocated_ptr; \
  80.       (string_array)->args[(string_array)->count] = ((str) != NULL ? strdup ((str)) : NULL); \
  81.       if ((str) != NULL) \
  82.          ASSERT_WITH_ERRNO ((string_array)->args[(string_array)->count]); \
  83.       (string_array)->count++; \
  84.    } while (0)
  85. #define STRINGARRAY_FREE(string_array) do { \
  86.       if ((string_array)->args != NULL) { \
  87.          for (array_index = 0; array_index < (string_array)->count; array_index++) \
  88.             if ((string_array)->args[array_index] != NULL) \
  89.                free ((string_array)->args[array_index]); \
  90.          free ((string_array)->args); \
  91.          (string_array)->args = NULL; \
  92.       } \
  93.       (string_array)->count = 0; \
  94.    } while (0)
  95.  
  96.  
  97. // string array structure type definition
  98. typedef struct stringarray_s
  99. {
  100.    char **args;
  101.    size_t count;
  102. } stringarray_t;
  103.  
  104.  
  105. // IFS directory entry insertion parameters structure type definition
  106. typedef struct parms_s
  107. {
  108.    int dperms; // directory permissions (e.g. 0755)
  109.    int perms; // file permissions (e.g. 0644)
  110.    int uid; // owner user ID (e.g. 0 = root)
  111.    int gid; // owner group ID (e.g. 0 = root)
  112.    int st_mode; // entry type (e.g. S_IFREG for files) and permissions
  113.    uint32_t mtime; // entry's modification time POSIX timestamp - set to UINT32_MAX to use the concerned files' mtime on the build host
  114.    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)
  115.    char *prefix; // [prefix=path] install path (e.g. "proc/boot")
  116.    bool should_follow_symlinks; // follow symlinks
  117.    bool should_autosymlink_dylib; // dynamic libraries should be written under their official SONAME and a named symlink be created pointing at them
  118.    bool should_keep_ld_output; // whether to keep .sym files produced by ld calls, togglable by the [+keeplinked] attribute
  119.    bool should_ignore_duplicates; // [+|-dupignore] whether to ignore duplicates
  120.    bool should_allow_nonexistent_files; // [+|-optional] whether to continue processing on unexistent files
  121.    bool is_bootstrap_file; // entry has the [virtual] attribute
  122.    bool is_compiled_bootscript; // entry has [+script] attribute
  123.    int extra_ino_flags; // bitmap of extra inode flags (IFS_INO_xxx)
  124.    char *search; // [search=path[:path]] binary search path (the default one will be constructed at startup)
  125.  
  126.    buffer_t data; // the resolved file's own data bytes
  127. } parms_t;
  128.  
  129.  
  130. // exported globals
  131. int verbose_level = 1; // verbosity level, can be increased with multiple -v[...] flags
  132.  
  133.  
  134. // global variables used in this module only
  135. static char line_buffer[4096]; // scrap buffer for the IFS build file parser
  136. 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
  137. static uint32_t image_end = UINT32_MAX; // default image end (no limit)
  138. static uint32_t image_maxsize = UINT32_MAX; // default image max size (no limit)
  139. static uint32_t image_totalsize = 0; // image total size, measured once all the blocks have been written to the output IFS file
  140. static uint32_t image_align = 4; // default image alignment, as per QNX docs
  141. static uint32_t image_kernel_ino = 0;
  142. static uint32_t image_bootscript_ino = 0;
  143. static int startup_header_compression_flag = STARTUP_HDR_FLAGS1_COMPRESS_NONE;
  144. #if defined(__x86_64__)
  145. static char *image_processor = "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)
  146. static char *image_processor_base = "x86_64"; // default base 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)
  147. static size_t image_pagesize = 4 * 1024; // default page size for the image, depends on the CPU type. Intel has 4kb pages, ARM has 16kb ones.
  148. #elif defined(__aarch64__)
  149. static char *image_processor = "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)
  150. static char *image_processor_base = "aarch64"; // default base 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)
  151. static size_t image_pagesize = 16 * 1024; // default page size for the image, depends on the CPU type. Intel has 4kb pages, ARM has 16kb ones.
  152. #else // unknown platform
  153. #error Please port ifstool to this platform
  154. #endif
  155. static char *buildfile_pathname = NULL; // pathname of IFS build file
  156. static char *current_line = NULL; // copy of current line in IFS build file
  157. static int lineno = 0; // current line number in IFS build file
  158. static char *QNX_TARGET = NULL; // value of the $QNX_TARGET environment variable
  159. static char *SEARCH_PATH = NULL; // mallocated string of search paths, populated by the -r command-line argument
  160. static char **saved_ELF_sections = NULL; // mallocated array of const strings, populated by the -s command-line argument
  161. static size_t saved_ELF_section_count = 0; // number of elements in the saved_ELF_sections array
  162. static char *sym_suffix = ""; // .sym files extra suffix, settable with the -a command-line argument
  163.  
  164. // bootable IFS support
  165. static int boot_type = BOOTTYPE_NONE;
  166. static char *bootfile_pathname = NULL;           // FIXME: HACK: pathname to bootcode binary blob file to put at the start of a bootable IFS in BIOS mode
  167. static size_t bootfile_size = 0;                 // FIXME: HACK: size of the bootcode binary blob file to put at the start of a bootable IFS in BIOS mode
  168. static char *startupfile_pathname = NULL;        // FIXME: HACK: pathname to precompiled startup file blob to put in the startup header of a bootable IFS
  169. static size_t startupfile_ep_from_imagebase = 0; // FIXME: HACK: startup code entrypoint offset from image base for a bootable IFS
  170. static size_t kernelfile_offset = 0;             // kernel file offset in the IFS (first offset rounded at pagesize after the dirents table)
  171.  
  172.  
  173. // exported function prototypes
  174. 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
  175.  
  176.  
  177. // prototypes of local functions
  178. static long long read_integer (const char *str, const int base_or_zero_for_auto); // 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)
  179. static char *resolve_envvars (const char *str); // resolves environment variables in str and replaces them with their value, or an empty string if undefined. Returns a mallocated string (caller frees)
  180. static char *resolve_pathname (const char *pathname, const char *search_paths_or_NULL_for_MKIFS_PATH_envvar); // locates pathname among the known search paths and returns a pointer to the resolved pathname (static string)
  181. 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
  182. 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
  183. static size_t Buffer_AppendIFSFileData (buffer_t *ifs_data, const fsentry_t *fsentry); // writes the given filesystem entry's file data (i.e. its contents) to the IFS buffer
  184. static int Buffer_StripELFFile (buffer_t *file, const char **saved_sections, const size_t saved_section_count, const bool should_align_segsize_with_ramsize, const char *indicative_pathname); // strips an ELF file buffer the way mkifs does it and returns whether it succeeded
  185. static void 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 in the the fsentries array
  186. static void add_directory_contents_recursively (fsentry_t **fsentries, size_t *fsentry_count, const char *dir_pathname, const size_t start_pathname_len, parms_t *default_parms); // adds the contents of the directory pointed to by dir_pathname to the fsentries array, recursively
  187. static int fsentry_compare_pathnames_cb (const void *a, const void *b); // qsort() comparison callback that sorts filesystem entries by pathnames
  188. static void parse_line (FILE *buildfile_fp, char *line_buffer, fsentry_t **fsentries, size_t *fsentry_count, parms_t *default_parms); // parses a line in the build file and make the relevant changes to the fsentries array
  189.  
  190.  
  191. // imported function prototypes
  192. extern int dump_ifs_info (const char *ifs_pathname, bool want_everything, bool hide_filename); // [implemented in ifsdump.c] dumps detailed info about a particular IFS file on the standard output, returns 0 on success and >0 on error
  193. 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
  194. 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
  195.  
  196.  
  197. int32_t update_checksum (const void *data, const size_t data_len, const bool is_foreign_endianness)
  198. {
  199.    // 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
  200.  
  201.    uint8_t accumulator[4] = { 0, 0, 0, 0 };
  202.    const char *current_char_ptr;
  203.    int32_t image_cksum;
  204.    size_t i;
  205.  
  206.    image_cksum = 0;
  207.    current_char_ptr = data;
  208.    for (i = 0; i < data_len; i++)
  209.    {
  210.       accumulator[i % 4] = *current_char_ptr;
  211.       if (i % 4 == 3)
  212.          if (is_foreign_endianness)
  213.             image_cksum += (accumulator[3] << 0) + (accumulator[2] << 8) + (accumulator[1] << 16) + (accumulator[0] << 24);
  214.          else
  215.             image_cksum += (accumulator[0] << 0) + (accumulator[1] << 8) + (accumulator[2] << 16) + (accumulator[3] << 24);
  216.       current_char_ptr++;
  217.    }
  218.  
  219.    return (is_foreign_endianness ? __builtin_bswap32 (-image_cksum) : -image_cksum);
  220. }
  221.  
  222.  
  223. static long long read_integer (const char *str, const int base_or_zero_for_auto)
  224. {
  225.    // 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)
  226.  
  227.    char *endptr = NULL;
  228.    long long ret = strtoll (str, &endptr, base_or_zero_for_auto); // use strtoll() to handle hexadecimal (0x...), octal (0...) and decimal (...) bases
  229.    if (endptr != NULL)
  230.    {
  231.       if      ((*endptr == 'k') || (*endptr == 'K')) ret *= (size_t) 1024;
  232.       else if ((*endptr == 'm') || (*endptr == 'M')) ret *= (size_t) 1024 * 1024;
  233.       else if ((*endptr == 'g') || (*endptr == 'G')) ret *= (size_t) 1024 * 1024 * 1024;
  234.       else if ((*endptr == 't') || (*endptr == 'T')) ret *= (size_t) 1024 * 1024 * 1024 * 1024; // future-proof enough, I suppose?
  235.    }
  236.    return (ret);
  237. }
  238.  
  239.  
  240. static char *resolve_envvars (const char *str)
  241. {
  242.    // resolves environment variables in str and replaces them with their value, or an empty string if undefined
  243.    // returns a mallocated string (caller frees), or dies with errno
  244.  
  245.    signed int erase_index;
  246.    void *reallocated_ptr;
  247.    size_t replacement_len;
  248.    size_t middlebit_len;
  249.    size_t old_str_len;
  250.    size_t new_str_len;
  251.    size_t endbit_len;
  252.    char erased_char;
  253.    char *resolved_str;
  254.    char *replacement;
  255.    char *varname;
  256.    char *endbit;
  257.    char *token;
  258.  
  259.    resolved_str = strdup (str); // have a working copy of the input string
  260.    ASSERT_WITH_ERRNO (resolved_str);
  261.    while ((((token = strstr (resolved_str, "${")) != NULL) && ((endbit = strchr (token, '}')) != NULL)) // look for variables in the "${VARNAME}" format *AND* in "$VARNAME" format
  262.           || (((token = strstr (resolved_str, "$")) != NULL) && ((middlebit_len = strspn (token, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_")) != strlen (token))))
  263.    {
  264.       if (token[1] == '{') // "${VARNAME}" format
  265.       {
  266.          endbit++; // locate where the end bit begins
  267.          varname = token + 2; // skip the leading two characters: "${"
  268.          erase_index = -1; // we shall split the string at the character that's *just before* where the end bit starts
  269.       }
  270.       else // "$VARNAME" format
  271.       {
  272.          endbit = &token[middlebit_len]; // locate where the end bit begins
  273.          varname = token + 1; // skip the leading '$'
  274.          erase_index = 0; // we shall split the string at the character that's *right where* the end bit starts
  275.       }
  276.       old_str_len = strlen (resolved_str); // measure current string length
  277.       endbit_len = strlen (endbit); // measure the length of the end bit (skip the closing curly brace)
  278.       erased_char = endbit[erase_index]; // remember which is the character we're going to erase
  279.       endbit[erase_index] = 0; // split the string at the end of the variable name
  280.       if (strcmp (varname, "PFS") == 0)
  281.          replacement = PATH_SEP; // special case: if it's the PFS variable, select ":" or ";" based on the host platform
  282.       else if (strcmp (varname, "PROCESSOR") == 0)
  283.          replacement = image_processor; // special case: if it's PROCESSOR, replace it with the processor name (e.g. "aarch64le")
  284.       else if (strcmp (varname, "PROCESSOR_BASE") == 0)
  285.          replacement = image_processor_base; // special case: if it's PROCESSOR_BASE, replace it with the processor base name (e.g. "aarch64")
  286.       else
  287.          replacement = getenv (varname); // peek at the environment for its value
  288.       if (replacement == NULL)
  289.          replacement = ""; // if this variable isn't defined, fallback to an empty string, just like what a UNIX shell does
  290.       endbit[erase_index] = erased_char; // put the erased character back
  291.       replacement_len = strlen (replacement); // measure replacement length
  292.       new_str_len = (size_t) token - (size_t) resolved_str + replacement_len + endbit_len; // measure updated string len
  293.       if (new_str_len > old_str_len)
  294.       {
  295.          reallocated_ptr = realloc (resolved_str, new_str_len + 1); // grow it if necessary
  296.          ASSERT_WITH_ERRNO (reallocated_ptr);
  297.          token = &((char *) reallocated_ptr)[token - resolved_str]; // fix the pointers that may have moved
  298.          endbit = &((char *) reallocated_ptr)[endbit - resolved_str]; // fix the pointers that may have moved
  299.          resolved_str = reallocated_ptr;
  300.       }
  301.       memmove (token + replacement_len, endbit, endbit_len + 1); // move the end bit to its final location (including its nul terminator)
  302.       memcpy (token, replacement, replacement_len); // and patch the replacement in between
  303.    }
  304.  
  305.    return (resolved_str); // finished, return the mallocated resolved string (caller frees)
  306. }
  307.  
  308.  
  309. static char *resolve_pathname (const char *pathname, const char *search_paths_or_NULL_for_MKIFS_PATH_envvar)
  310. {
  311.    // locates pathname among search path and returns resolved pathname (static buffer) or NULL.
  312.  
  313.    typedef struct default_path_s { bool uses_processor_base; char *subpath; } default_path_t;
  314.  
  315.    static const default_path_t default_paths[] =
  316.    {
  317.       { false, "/sbin"     }, // prefix with $PROCESSOR/
  318.       { false, "/usr/sbin" }, // prefix with $PROCESSOR/
  319.       { false, "/boot/sys" }, // prefix with $PROCESSOR/
  320.       { true,  "/boot/sys" }, // prefix with $PROCESSOR_BASE/
  321.       { false, "/bin"      }, // prefix with $PROCESSOR/
  322.       { false, "/usr/bin"  }, // prefix with $PROCESSOR/
  323.       { false, "/lib"      }, // prefix with $PROCESSOR/
  324.       { false, "/lib/dll"  }, // prefix with $PROCESSOR/
  325.       { false, "/usr/lib"  }  // prefix with $PROCESSOR/
  326.    };
  327.    static thread_local char *resolved_pathname = NULL;
  328.  
  329.    char *pathname_without_envvars;
  330.    char *resolved_search_path;
  331.    size_t defaultpath_index;
  332.    struct stat stat_buf;
  333.    char *nextsep;
  334.    char *token;
  335.  
  336.    // resolve possible environment variables in pathname
  337.    pathname_without_envvars = resolve_envvars (pathname);
  338.  
  339.    // NOTE: the QNX documentation states:
  340.    // "- If path starts with a slash (/) on a Linux development host, or a disk volume label (i.e., drive letter and a colon) followed by a backslash (\) on a Windows host, the path is absolute and mkifs looks for the file at that exact host location. [...]
  341.    //  - If path contains a slash or backslash character that's not at the start, the path is relative and mkifs tries to resolve it relative to the current working directory (CWD).
  342.    //  - If path does not contain a directory separator or the file could not be found relative to the CWD, mkifs tries to resolve it relative to all directories given in the search attribute, in succession."
  343.  
  344.    // is it an absolute pathname (POSIX and Windows variants) ?
  345.    if (IS_DIRSEP (pathname_without_envvars[0])
  346. #ifdef _WIN32
  347.        || (isalpha (pathname_without_envvars[0]) && (pathname_without_envvars[1] == ':') && IS_DIRSEP (pathname_without_envvars[2]))
  348. #endif // _WIN32
  349.        )
  350.       return (pathname_without_envvars); // in this case, it MUST exist at its designated location
  351.  
  352.    // else is it a relative pathname ?
  353.    else if (((strchr (pathname_without_envvars, '/') != NULL)
  354. #ifdef _WIN32
  355.              || (strchr (pathname_without_envvars, '\\') != NULL)
  356. #endif // _WIN32
  357.             ) && (stat (pathname_without_envvars, &stat_buf) == 0) && S_ISREG (stat_buf.st_mode))
  358.       return (pathname_without_envvars); // in this case, see if it exists relatively to the current working directory, and if it does, return it
  359.  
  360.    // what we've been given is just a basename, so search it among the search paths we have
  361.  
  362.    // QNX docs:
  363.    // When searching for host files to be included in the image, search the default paths used for storing binaries within the specified directory before searching the default paths within $QNX_TARGET.
  364.    // You can define multiple -r options; each adds a set of paths to search for files.
  365.    // The -r options are evaluated from left to right meaning the paths prefixed with the first (leftmost) rootdir are searched first, then those prefixed with the second rootdir, and so on.
  366.    // Normally, mkifs searches any paths defined in $MKIFS_PATH when it was called and then the default paths within $QNX_TARGET.
  367.    // The default paths are based on the CPU architecture specified by $PROCESSOR and $PROCESSOR_BASE.
  368.    // If you specify -r options, mkifs searches the default paths prefixed with each dir variable before searching those within $QNX_TARGET.
  369.    // These paths are:
  370.    //   dir/${PROCESSOR}/sbin
  371.    //   dir/${PROCESSOR}/usr/sbin
  372.    //   dir/${PROCESSOR}/boot/sys
  373.    //   dir/${PROCESSOR_BASE}/boot/sys
  374.    //   dir/${PROCESSOR}/bin
  375.    //   dir/${PROCESSOR}/usr/bin
  376.    //   dir/${PROCESSOR}/lib
  377.    //   dir/${PROCESSOR}/lib/dll
  378.    //   dir/${PROCESSOR}/usr/lib
  379.    // NOTE: The structure of the directory paths under dir must be identical to that of the default paths under $QNX_TARGET, but the root dir itself may be any path you choose.
  380.    // For example, if you wanted to include /scratch/aarch64le/sbin/devb-sata, you would specify a -r option like this:
  381.    //   -r /scratch
  382.    // Note that you don't include $PROCESSOR or $PROCESSOR_BASE in dir.
  383.  
  384.    //  - search all paths in explicit path/[default paths] (if explicit path supplied)
  385.    //  - search all paths in (-r flags if have some|MKIFS_PATH)/[default paths] (if no explicit path supplied)
  386.    //  - search all paths in $QNX_TARGET/[default paths]
  387.  
  388.    // initial allocation (per thread)
  389.    if (resolved_pathname == NULL)
  390.    {
  391.       resolved_pathname = malloc (MAXPATHLEN);
  392.       ASSERT_WITH_ERRNO (resolved_pathname);
  393.    }
  394.  
  395.    // if no file-specific explicit search path was supplied, use the path list supplied by the -r command-line arguments, else fallback to MKIFS_PATH if we don't have any
  396.    if (search_paths_or_NULL_for_MKIFS_PATH_envvar == NULL)
  397.       search_paths_or_NULL_for_MKIFS_PATH_envvar = (SEARCH_PATH != NULL ? SEARCH_PATH : getenv ("MKIFS_PATH"));
  398.  
  399.    // construct a potential final path using each element of the search path
  400.    if (search_paths_or_NULL_for_MKIFS_PATH_envvar != NULL)
  401.    {
  402.       // the first step is to resolve all environment variables in the search path
  403.       resolved_search_path = resolve_envvars (search_paths_or_NULL_for_MKIFS_PATH_envvar);
  404.  
  405.       // now split this search path string into multiple tokens and process them one after the other
  406.       token = (*resolved_search_path != 0 ? resolved_search_path : NULL);
  407.       nextsep = (token != NULL ? &token[strcspn (token, PATH_SEP)] : NULL);
  408.       while (token != NULL)
  409.       {
  410.          // look under this search path at each of the known subpaths
  411.          for (defaultpath_index = 0; defaultpath_index < sizeof (default_paths) / sizeof (default_paths[0]); defaultpath_index++)
  412.          {
  413.             sprintf_s (resolved_pathname, MAXPATHLEN, "%.*s/%s/%s/%s", (int) (nextsep - token), token, (default_paths[defaultpath_index].uses_processor_base ? image_processor_base : image_processor), default_paths[defaultpath_index].subpath, pathname_without_envvars);
  414.             if ((stat (resolved_pathname, &stat_buf) == 0) && S_ISREG (stat_buf.st_mode))
  415.             {
  416.                free (pathname_without_envvars);
  417.                return (resolved_pathname); // if a file can indeed be found at this location, stop searching
  418.             }
  419.          }
  420.  
  421.          token = (*nextsep != 0 ? nextsep + 1 : NULL);
  422.          nextsep = (token != NULL ? &token[strcspn (token, PATH_SEP)] : NULL);
  423.       }
  424.    }
  425.  
  426.    // file not found in search paths: look under QNX_TARGET at each of the known subpaths
  427.    for (defaultpath_index = 0; defaultpath_index < sizeof (default_paths) / sizeof (default_paths[0]); defaultpath_index++)
  428.    {
  429.       sprintf_s (resolved_pathname, MAXPATHLEN, "%s/%s/%s/%s", QNX_TARGET, (default_paths[defaultpath_index].uses_processor_base ? image_processor_base : image_processor), default_paths[defaultpath_index].subpath, pathname_without_envvars);
  430.       if ((stat (resolved_pathname, &stat_buf) == 0) && S_ISREG (stat_buf.st_mode))
  431.       {
  432.          free (pathname_without_envvars);
  433.          return (resolved_pathname); // if a file can indeed be found at this location, stop searching
  434.       }
  435.    }
  436.  
  437.    free (pathname_without_envvars);
  438.    errno = ENOENT; // we exhausted all possibilities
  439.    return (NULL); // file not found, return with ENOENT
  440. }
  441.  
  442.  
  443. static size_t Buffer_WriteIFSDirectoryEntryAt (buffer_t *ifs, const size_t write_offset, const fsentry_t *fsentry)
  444. {
  445.    // writes a directory entry in the image filesystem buffer pointed to by ifs at write_offset (or fakes so if ifs is NULL)
  446.    // and return the number of bytes written (or that would have been written)
  447.  
  448.    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";
  449.  
  450.    size_t datalen;
  451.    size_t count;
  452.  
  453.    count = 0;
  454.    if (ifs != NULL)
  455.       ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, &fsentry->header, sizeof (fsentry->header))); // write the entry header (PACKED STRUCT)
  456.    count += sizeof (fsentry->header);
  457.    if (S_ISREG (fsentry->header.mode))
  458.    {
  459.       if (ifs != NULL)
  460.          ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, &fsentry->u.file.offset, sizeof (uint32_t))); // write offset
  461.       count += sizeof (uint32_t);
  462.       if (ifs != NULL)
  463.          ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, &fsentry->u.file.size,   sizeof (uint32_t))); // write size
  464.       count += sizeof (uint32_t);
  465.       datalen = strlen (fsentry->u.file.path) + 1;
  466.       if (ifs != NULL)
  467.          ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, fsentry->u.file.path, datalen)); // write null-terminated path (no leading slash)
  468.       count += datalen;
  469.    }
  470.    else if (S_ISDIR (fsentry->header.mode))
  471.    {
  472.       datalen = strlen (fsentry->u.dir.path) + 1;
  473.       if (ifs != NULL)
  474.          ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, fsentry->u.dir.path, datalen)); // write null-terminated path (no leading slash)
  475.       count += datalen;
  476.    }
  477.    else if (S_ISLNK (fsentry->header.mode))
  478.    {
  479.       if (ifs != NULL)
  480.          ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, &fsentry->u.symlink.sym_offset, sizeof (uint16_t))); // write offset
  481.       count += sizeof (uint16_t);
  482.       if (ifs != NULL)
  483.          ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, &fsentry->u.symlink.sym_size,   sizeof (uint16_t))); // write size
  484.       count += sizeof (uint16_t);
  485.       datalen = strlen (fsentry->u.symlink.path) + 1;
  486.       if (ifs != NULL)
  487.          ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, fsentry->u.symlink.path, datalen)); // write null-terminated path (no leading slash)
  488.       count += datalen;
  489.       datalen = strlen (fsentry->u.symlink.contents) + 1;
  490.       if (ifs != NULL)
  491.          ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, fsentry->u.symlink.contents, datalen)); // write null-terminated symlink contents
  492.       count += datalen;
  493.    }
  494.    else
  495.    {
  496.       if (ifs != NULL)
  497.          ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, &fsentry->u.device.dev,  sizeof (uint32_t))); // write dev number
  498.       count += sizeof (uint32_t);
  499.       if (ifs != NULL)
  500.          ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, &fsentry->u.device.rdev, sizeof (uint32_t))); // write rdev number
  501.       count += sizeof (uint32_t);
  502.       datalen = strlen (fsentry->u.device.path) + 1;
  503.       if (ifs != NULL)
  504.          ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, fsentry->u.device.path, datalen)); // write null-terminated path (no leading slash)
  505.       count += datalen;
  506.    }
  507.  
  508.    ASSERT (count <= fsentry->header.size, "attempt to write invalid dirent (claimed size %zd, written size %zd). Aborting.", (size_t) fsentry->header.size, count);
  509.    if (count < fsentry->header.size)
  510.    {
  511.       if (ifs != NULL)
  512.          ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, zeropad_buffer, fsentry->header.size - count)); // pad as necessary
  513.       count += fsentry->header.size - count;
  514.    }
  515.  
  516.    return (count);
  517. }
  518.  
  519.  
  520. static size_t Buffer_AppendIFSFileData (buffer_t *ifs_data, const fsentry_t *fsentry)
  521. {
  522.    // writes the given filesystem entry's file data (i.e. its contents) to the IFS buffer
  523.  
  524.    elf_program_header_t *phdr;
  525.    elf_header_t *elf;
  526.    size_t fixed_physical_addr;
  527.    size_t corrective_offset;
  528.    //size_t segment_type;
  529.    size_t size_in_memory;
  530.    size_t table_index;
  531.    size_t table_count;
  532.    size_t data_offset;
  533.  
  534.    ASSERT (S_ISREG (fsentry->header.mode), "function called for invalid dirent"); // consistency check
  535.    data_offset = ifs_data->size; // see where we are
  536.  
  537.    // is the file we're storing a preprocessed ELF file ?
  538.    if (fsentry->header.ino & IFS_INO_PROCESSED_ELF)
  539.    {
  540.  
  541.       elf = (elf_header_t *) fsentry->UNSAVED_databuf; // quick access to ELF header
  542.       table_count = ELF_GET_NUMERIC (elf, elf, program_header_table_len); // get the number of program headers
  543.       for (table_index = 0; table_index < table_count; table_index++)
  544.       {
  545.          phdr = (elf_program_header_t *) &fsentry->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
  546.          //segment_type = ELF_GET_NUMERIC (elf, phdr, segment_type); // get segment type
  547.          //if (!((segment_type >= 2) && (segment_type <= 7) || ((segment_type >= 0x6474e550) && (segment_type <= 0x6474e552)) || (segment_type == 0x70000001)))
  548.          //   continue; // NOTE: only certain segments types must be corrected
  549.  
  550.  
  551.          corrective_offset = ELF_GET_NUMERIC (elf, phdr, virtual_addr) - ELF_GET_NUMERIC (elf, phdr, file_offset);
  552.          size_in_memory = ELF_GET_NUMERIC (elf, phdr, size_in_memory); // get this ELF segment's occupied size in memory
  553.          if (size_in_memory != 0) // only patch the physical address of segments that have an actual size in memory
  554.          {
  555.             fixed_physical_addr = ELF_GET_NUMERIC (elf, phdr, physical_addr) + image_base + data_offset - corrective_offset;
  556.             ELF_SET_NUMERIC (elf, phdr, physical_addr, fixed_physical_addr); // 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)
  557.          }
  558.       }
  559.    }
  560.  
  561.    ASSERT_WITH_ERRNO (Buffer_Append (ifs_data, fsentry->UNSAVED_databuf, fsentry->u.file.size)); // write file data blob
  562.    return (ifs_data->size - data_offset); // return the number of bytes written
  563. }
  564.  
  565.  
  566. static inline size_t Buffer_LocateOrAppendIfNecessaryAndReturnOffsetOf (buffer_t *buffer, const char *str)
  567. {
  568.    // helper function used in add_fsentry(): locates or appends str to buffer and returns its relative offset in the buffer
  569.  
  570.    size_t str_len_including_terminator = strlen (str) + 1;
  571.    void *occurrence = Buffer_FindFirst (buffer, str, str_len_including_terminator);
  572.    if (occurrence == NULL)
  573.    {
  574.       ASSERT_WITH_ERRNO (Buffer_Append (buffer, str, str_len_including_terminator));
  575.       occurrence = Buffer_FindFirst (buffer, str, str_len_including_terminator);
  576.       ASSERT_WITH_ERRNO (occurrence);
  577.    }
  578.    return (Buffer_OffsetOf (buffer, occurrence)); // can't fail
  579. }
  580.  
  581.  
  582. static int Buffer_StripELFFile (buffer_t *file, const char **saved_sections, const size_t saved_section_count, const bool should_align_segsize_with_ramsize, const char *indicative_pathname)
  583. {
  584.    // NOTE: for each ELF file, mkifs
  585.    // -> 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)
  586.    // -> 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
  587.    // FIXME: what if a thrown away section is located between two program segments ? are they collapsed, moving the segments beyond it one slot down ?
  588.  
  589.    // reconstructed ELF:
  590.    // ==== START OF FILE ====
  591.    // ELF header
  592.    // program header table
  593.    //  (same sections, just p_addr offset changed)
  594.    // section data 5 (named ".note.gnu.build-id")
  595.    //  "............GNU....ZY.....c.o..l"
  596.    // PROGRAM
  597.    // sections table
  598.    // + section 1: ALL ZEROES
  599.    // + 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"
  600.    // + 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"
  601.    // + 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.."
  602.    // + section 5: fileoffs 0x190 size 0x32 --> ".note.gnu.build-id" --> GNU build ID
  603.    // + section 6: fileoffs 0x256e size 0x40 --> ".shstrtab" --> sections names strings table
  604.    // section data 2 (named "QNX_info")
  605.    //  (QNX binary description)
  606.    // section data 3 (named ".gnu_debuglink")
  607.    //  (debug file)
  608.    // section data 4 (named "QNX_usage")
  609.    //  (help text)
  610.    // section data 6 (named ".shstrtab")
  611.    //  "\0"
  612.    //  ".shstrtab\0"
  613.    //  "QNX_info\0"
  614.    //  ".gnu_debuglink\0"
  615.    //  "QNX_usage\0"
  616.    //  ".note.gnu.build-id\0"
  617.    // ==== END OF FILE ====
  618.  
  619.    #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
  620.    #define ADD_SECTION(section_name,section_ptr) do { \
  621.       void *reallocated_ptr = realloc (elf_sections, (elf_section_count + 1) * sizeof (elf_section_t)); \
  622.       ASSERT_WITH_ERRNO (reallocated_ptr); \
  623.       elf_sections = reallocated_ptr; \
  624.       elf_sections[elf_section_count].name = (section_name); \
  625.       Buffer_Initialize (&elf_sections[elf_section_count].data); \
  626.       *(section_ptr) = &elf_sections[elf_section_count]; \
  627.       elf_section_count++; \
  628.    } while (0)
  629.  
  630.    typedef struct elf_section_s
  631.    {
  632.       const char *name;
  633.       elf_section_header_t header;
  634.       buffer_t data;
  635.    } elf_section_t;
  636.  
  637.    const elf_section_header_t *shdr;
  638.    elf_program_header_t *phdr;
  639.    elf_program_header_t *other_phdr;
  640.    elf_section_t *elf_sections = NULL; // mallocated
  641.    elf_section_t *elf_section = NULL;
  642.    size_t elf_section_count = 0;
  643.    size_t new_shdrtable_offset;
  644.    size_t new_shdrtable_len;
  645.    size_t sectiondata_start;
  646.    size_t sectiondata_size;
  647.    size_t size_in_memory;
  648.    size_t size_in_file;
  649.    size_t file_offset;
  650.    size_t array_index;
  651.    size_t table_index;
  652.    size_t table_count;
  653.  
  654.    // if we should align the segment sizes in the ELF file with their occupied memory size (such is the case for e.g. procnto), do that first
  655.    table_count = ELF_GET_NUMERIC (ELFHDR, ELFHDR, program_header_table_len); // get the number of program headers
  656.    for (table_index = 0; table_index < table_count; table_index++)
  657.    {
  658.       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
  659.       file_offset    = ELF_GET_NUMERIC (ELFHDR, phdr, file_offset); // get this ELF segment's start offset in the ELF file
  660.       size_in_memory = ELF_GET_NUMERIC (ELFHDR, phdr, size_in_memory); // get this ELF segment's occupied size in memory
  661.       size_in_file   = ELF_GET_NUMERIC (ELFHDR, phdr, size_in_file); // get this ELF segment's occupied size in the ELF file
  662.       if (should_align_segsize_with_ramsize && (size_in_memory != size_in_file)) // should we align this segment's file size with its claimed RAM size ? (such is the case for e.g. procnto)
  663.       {
  664.          if (size_in_memory > size_in_file) // is it bigger ? if so, make sure we won't be overwriting other segments beyond this one
  665.          {
  666.             for (array_index = 0; array_index < table_count; array_index++)
  667.             {
  668.                other_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) * array_index]; // quick access to program header
  669.                if (other_phdr == phdr)
  670.                   continue; // skip self
  671.                if (ELF_GET_NUMERIC (ELFHDR, other_phdr, file_offset) + ELF_GET_NUMERIC (ELFHDR, other_phdr, size_in_file) < file_offset)
  672.                   continue; // skip segments that are located before this one
  673.                if (ELF_GET_NUMERIC (ELFHDR, other_phdr, file_offset) > file_offset + size_in_memory)
  674.                   continue; // skip segments that are located after this one, including its corrected size
  675.                DIE_WITH_EXITCODE (1, "remapping ELF segment would overwrite segment #%zd in the same file", array_index);
  676.             }
  677.  
  678.             // finally, memset() the extra area
  679.             Buffer_WriteAt (file, file_offset + size_in_memory, NULL, 0); // reallocate the ELF file data buffer if necessary
  680.             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]; // restore access to program header (which may have moved)
  681.             memset (&file->bytes[file_offset + size_in_file], 0, size_in_memory - size_in_file); // and write zeroes over the extra space
  682.          }
  683.          ELF_SET_NUMERIC (ELFHDR, phdr, size_in_file, size_in_memory); // patch this segment's size in the ELF file so that it matches the RAM size
  684.       }
  685.    }
  686.  
  687.    // now parse the program header table, and measure the farthest offset known by this table where we'll write the reconstructed section headers table
  688.    new_shdrtable_offset = 0;
  689.    table_count = ELF_GET_NUMERIC (ELFHDR, ELFHDR, program_header_table_len);
  690.    for (table_index = 0; table_index < table_count; table_index++)
  691.    {
  692.       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
  693.       if (ELF_GET_NUMERIC (ELFHDR, phdr, file_offset) + ELF_GET_NUMERIC (ELFHDR, phdr, size_in_file) > new_shdrtable_offset)
  694.          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
  695.    }
  696.    /*
  697.    size_t new_shdrtable_offset_method2 = 0;
  698.    for (table_index = 0; table_index < table_count; table_index++)
  699.    {
  700.       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
  701.       size_t segment_type = ELF_GET_NUMERIC (ELFHDR, phdr, segment_type); // get segment type
  702.       if (!((segment_type >= 2) && (segment_type <= 7)))
  703.          continue; // NOTE: only certain segments types must be corrected
  704.       if (ELF_GET_NUMERIC (ELFHDR, phdr, file_offset) + ELF_GET_NUMERIC (ELFHDR, phdr, size_in_memory) > new_shdrtable_offset_method2)
  705.          new_shdrtable_offset_method2 = ELF_GET_NUMERIC (ELFHDR, phdr, file_offset) + ELF_GET_NUMERIC (ELFHDR, phdr, size_in_memory);
  706.    }
  707.    if (new_shdrtable_offset_method2 > new_shdrtable_offset)
  708.       LOG_DEBUG ("METHOD2: %llx > %llx", new_shdrtable_offset_method2, new_shdrtable_offset);*/
  709.    //new_shdrtable_offset = ROUND_TO_UPPER_MULTIPLE (new_shdrtable_offset, image_pagesize); // round to page size
  710.  
  711.    // re-create the section header table
  712.    ADD_SECTION (".shstrtab", &elf_section); // the first section will be the section names strings table
  713.    ASSERT_WITH_ERRNO (Buffer_InitWithByteArray (&elf_section->data, "\0")); // initialize an empty section headers strings table
  714.    ASSERT_WITH_ERRNO (Buffer_AppendByteArray (&elf_section->data, ".shstrtab\0")); // append ".shstrtab" *INCLUDING* its null terminator
  715.  
  716.    // go through the saved sections array and see if such an ELF section is present in the ELF file
  717.    for (array_index = 0; array_index < saved_section_count; array_index++)
  718.       if ((shdr = elf_get_section_header_by_name (ELFHDR, saved_sections[array_index])) != NULL) // does this ELF have such a section ?
  719.       {
  720.          ADD_SECTION (saved_sections[array_index], &elf_section); // yes, so save it
  721.          sectiondata_start = ELF_GET_NUMERIC (ELFHDR, shdr, file_offset); // identify section data start offset
  722.          sectiondata_size = ELF_GET_NUMERIC (ELFHDR, shdr, size); // identify section data length
  723.          if (sectiondata_start + sectiondata_size >= new_shdrtable_offset) // should this section be moved ?
  724.             ASSERT_WITH_ERRNO (Buffer_InitWithData (&elf_section->data, &file->bytes[sectiondata_start], sectiondata_size)); // have a copy of this section's data
  725.          else
  726.             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
  727.          //LOG_DEBUG ("%s: section '%s' start 0x%llx len 0x%llx", indicative_pathname, saved_ELF_sections[array_index], (unsigned long long) sectiondata_start, (unsigned long long) sectiondata_size);
  728.  
  729.          // prepare this section's "fixed" header
  730.          memcpy (&elf_section->header, shdr, ELF_STRUCT_SIZE (ELFHDR, shdr)); // have a copy of the old section header first
  731.          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
  732.       }
  733.  
  734.    // jump over the new section headers table and write the saved sections data after the section headers table
  735.    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
  736.    for (table_index = 1; table_index < elf_section_count; table_index++)
  737.    {
  738.       elf_section = &elf_sections[table_index]; // quick access to ELF section about to be written
  739.       if (elf_section->data.bytes != NULL) // was this section data backed up waiting to be relocated ?
  740.       {
  741.          ELF_SET_NUMERIC (ELFHDR, &elf_section->header, file_offset, file->size); // fix section offset
  742.          Buffer_AppendBuffer (file, &elf_section->data); // append this section's data to the ELF file
  743.       }
  744.    }
  745.    // write the section header strings table as the last section
  746.    elf_section = &elf_sections[0]; // quick access to ELF section about to be written
  747.    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
  748.    ELF_SET_NUMERIC (ELFHDR, &elf_section->header, type, ELF_SECTIONTYPE_STRINGTABLE); // section type (SHT_STRTAB)
  749.    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)
  750.    ELF_SET_NUMERIC (ELFHDR, &elf_section->header, virtual_addr, 0); // this section does not need to be mapped
  751.    ELF_SET_NUMERIC (ELFHDR, &elf_section->header, file_offset, file->size); // fix section offset
  752.    ELF_SET_NUMERIC (ELFHDR, &elf_section->header, size, elf_sections[0].data.size); // section size
  753.    ELF_SET_NUMERIC (ELFHDR, &elf_section->header, linked_index, 0); // this section is not linked to any other
  754.    ELF_SET_NUMERIC (ELFHDR, &elf_section->header, info, 0); // this section has no additional info
  755.    ELF_SET_NUMERIC (ELFHDR, &elf_section->header, alignment, 1); // this section is byte-aligned
  756.    ELF_SET_NUMERIC (ELFHDR, &elf_section->header, entry_size, 0); // this section is not a table, so entry_size is zero
  757.    Buffer_AppendBuffer (file, &elf_section->data); // append section headers strings table section data to ELF file
  758.  
  759.    // now write the section headers table
  760.    memset (&file->bytes[new_shdrtable_offset], 0, ELF_STRUCT_SIZE (ELFHDR, &elf_sections[0].header)); // the first section header is always zerofilled
  761.    for (table_index = 1; table_index < elf_section_count; table_index++)
  762.       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
  763.    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
  764.  
  765.    // and finally fix the ELF master header
  766.    new_shdrtable_len = 1 + elf_section_count; // take in account that the first entry in the section headers table is empty
  767.    ELF_SET_NUMERIC (ELFHDR, ELFHDR, section_header_table_offset, new_shdrtable_offset);
  768.    ELF_SET_NUMERIC (ELFHDR, ELFHDR, section_header_table_len, new_shdrtable_len);
  769.    ELF_SET_NUMERIC (ELFHDR, ELFHDR, section_header_names_idx, elf_section_count); // the section headers strings table is the last section
  770.  
  771.    // align size with page size (4096 on x86, 16k on ARM), zerofilling the extra space
  772.    ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (file, ROUND_TO_UPPER_MULTIPLE (file->size, image_pagesize)));
  773.  
  774.    // cleanup
  775.    for (table_index = 0; table_index < elf_section_count; table_index++)
  776.       Buffer_Forget (&elf_sections[table_index].data); // free all sections' backing buffers
  777.  
  778.    #undef ELFHDR // undefine the macro that used to always point to the ELF header at the beginning of the file
  779.    return (1); // success
  780. }
  781.  
  782.  
  783. static void add_fsentry (fsentry_t **fsentries, size_t *fsentry_count, parms_t *entry_parms, const char *stored_pathname, const char *buildhost_pathname)
  784. {
  785.    static thread_local char *candidate_pathname = NULL;
  786.    static thread_local parms_t default_parms = { 0 };
  787.    static thread_local stringarray_t global_envp = { NULL, 0 };
  788.    static thread_local stringarray_t aps_partnames = { NULL, 0 };
  789.    static int inode_count = 0; // will be preincremented each time this function is called
  790.  
  791.    typedef struct scriptcmd_s
  792.    {
  793.       char *argv0;
  794.       int cpu_number;
  795.       bool is_external;
  796.       int priority;
  797.       int sched_policy;
  798.       int aps_partindex;
  799.       bool is_session_leader;
  800.       bool is_background_task;
  801.       bool has_debug_flag;
  802.    } scriptcmd_t;
  803.  
  804.    scriptcmd_t default_scriptcmd_params = { NULL, -1, false, -1, -1, -1, false, false, false };
  805.    scriptcmd_t current_scriptcmd_params = { 0 };
  806.    stringarray_t global_argv = { NULL, 0 };
  807.    stringarray_t line_argv = { NULL, 0 };
  808.    stringarray_t line_envp = { NULL, 0 };
  809.    stringarray_t startup_argv = { NULL, 0 };
  810.    stringarray_t startup_envp = { NULL, 0 };
  811.    stringarray_t procnto_argv = { NULL, 0 };
  812.    stringarray_t procnto_envp = { NULL, 0 };
  813.    stringarray_t linker_argv = { NULL, 0 };
  814.    const char *stored_pathname_without_leading_slash;
  815.    const char *original_stored_pathname = NULL;
  816.    const char *filename_bit;
  817.    buffer_t current_line;
  818.    buffer_t compiled_script;
  819.    buffer_t compiled_scriptline;
  820.    buffer_t *shstrtab = NULL;
  821.    const char *canonical_dylib_name;
  822.    const char *dynamic_strings; // strings table of the ".dynamic" section
  823.    const char *last_dirsep;
  824.    size_t array_index;
  825.    size_t line_index;
  826.    size_t fsentry_index;
  827.    size_t pathbit_len;
  828.    size_t wait_time;
  829.    char *resolved_pathname;
  830.    char *linebit_start;
  831.    char *write_ptr;
  832.    char *read_ptr;
  833.    char *token;
  834.    char *value;
  835.    char *ctx;
  836.    void *reallocated_ptr;
  837.    void *old_data;
  838.    bool is_quoted_context;
  839.    bool is_end_of_line;
  840.    struct stat stat_buf;
  841.    fsentry_t *fsentry;
  842.    int retval;
  843.  
  844.    // initial allocation (per thread)
  845.    if (candidate_pathname == NULL)
  846.    {
  847.       candidate_pathname = malloc (MAXPATHLEN);
  848.       ASSERT_WITH_ERRNO (candidate_pathname);
  849.    }
  850.  
  851.    if (S_ISDIR (entry_parms->st_mode)) // are we storing a directory ?
  852.    {
  853.       if ((buildhost_pathname != NULL) && (buildhost_pathname[0] != 0)) // was a source file pathname supplied ?
  854.       {
  855.          memcpy (&default_parms, entry_parms, sizeof (parms_t)); // apply current entry parameters when including a directory recursively
  856.          add_directory_contents_recursively (fsentries, fsentry_count, buildhost_pathname, strlen (buildhost_pathname), &default_parms); // if so, add this diretory contents recursively
  857.       }
  858.       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);
  859.    }
  860.    else if (S_ISLNK (entry_parms->st_mode)) // else are we storing a symbolic link ?
  861.    {
  862.       // do we already know the data for this data blob ?
  863.       if (entry_parms->data.bytes != NULL)
  864.       {
  865.          entry_parms->mtime = entry_parms->mtime_for_inline_files; // if so, set it a mtime equal to the mtime to use for inline files
  866.          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);
  867.       }
  868.       else if (buildhost_pathname != NULL) // else was a source file pathname supplied ?
  869.       {
  870.          entry_parms->data.bytes = malloc (MAXPATHLEN); // allocate enough space for symlink data
  871.          ASSERT_WITH_ERRNO (entry_parms->data.bytes);
  872.          retval = readlink (buildhost_pathname, entry_parms->data.bytes, MAXPATHLEN); // read symlink contents
  873.          ASSERT_WITH_ERRNO (retval > 0);
  874.          entry_parms->data.size = retval; // save symlink target length
  875.       }
  876.       else
  877.          DIE_WITH_EXITCODE (1, "unexpected code path: can't store a symlink without neither explicit contents nor a host pathname. This is a bug in the program. Please contact the author.");
  878.    }
  879.    else if (S_ISFIFO (entry_parms->st_mode)) // else are we storing a FIFO ?
  880.    {
  881.       if ((entry_parms->data.bytes == NULL) || (strchr (entry_parms->data.bytes, ':') == NULL))
  882.          DIE_WITH_EXITCODE (1, "device entry \"%s\" malformed (no 'dev:rdev' pair)", stored_pathname);
  883.       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);
  884.    }
  885.    else // necessarily a regular file (either S_IFREG is specified, or st_mode is zero)
  886.    {
  887.       entry_parms->st_mode |= S_IFREG; // make this explicit
  888.  
  889.       if (entry_parms->is_bootstrap_file) // is it the bootstrap file ?
  890.       {
  891.          // parse each line of contents
  892.          ASSERT (entry_parms->data.size > 0, "kernel specification without inline contents");
  893.  
  894.          // parse buffer (non-destructively) line after line
  895.          Buffer_Initialize (&current_line);
  896.          for (line_index = 0; Buffer_GetNthLine (&entry_parms->data, line_index, &current_line); line_index++)
  897.          {
  898.             read_ptr = current_line.bytes;
  899.             while (isspace (*read_ptr))
  900.                read_ptr++; // skip leading spaces
  901.             if ((*read_ptr == '#') || (*read_ptr == 0))
  902.                continue; // skip comments and empty lines
  903.  
  904.             // format of a line: [attributes] [env assignation] [...] [executable] [arg] [...] [comment]
  905.             // example: "[uid=0 gid=0 perms=0700] CONFIG_PATH=/proc/boot:/etc procnto-smp-instr -v -mr -d 0777 -u 0777"
  906.  
  907.             LOG_DEBUG ("parsing line: %s", read_ptr);
  908.  
  909.             // does this line start with an attribute block ?
  910.             if (*read_ptr == '[')
  911.             {
  912.                read_ptr++; // skip the leading square bracket
  913.                linebit_start = read_ptr; // remember where it starts
  914.                is_quoted_context = false; // reach the next unescaped closing square bracket that is not between quotes
  915.                while ((*read_ptr != 0) && !((*read_ptr == ']') && (read_ptr[-1] != '\\') && !is_quoted_context))
  916.                {
  917.                   if (*read_ptr == '"')
  918.                      is_quoted_context ^= true; // remember when we're between quotes
  919.                   else if (!is_quoted_context && (*read_ptr == ' '))
  920.                      *read_ptr = RECORD_SEP[0]; // turn all spaces outside quoted contexts into an ASCII record separator to ease token splitting
  921.                   read_ptr++; // reach the next unescaped closing square bracket
  922.                }
  923.                if (*read_ptr != ']')
  924.                {
  925.                   LOG ("warning", 0, "syntax error in \"%s\" line %zd of inline document '%s': unterminated attributes block (skipping)", buildfile_pathname, 1 + line_index, stored_pathname);
  926.                   continue; // invalid attribute block, skip line
  927.                }
  928.                is_end_of_line = (*read_ptr == 0); // see if we're at the end of line already
  929.                *read_ptr = 0; // end the attribute block in all cases so that it is a parsable C string
  930.  
  931.                // now parse the attribute tokens (NOTE: THE LIST OF ALLOWED ATTRIBUTES HERE IS NOT DOCUMENTED)
  932.                token = strtok_r (linebit_start, RECORD_SEP, &ctx);
  933.                while (token != NULL)
  934.                {
  935.                   #define REACH_TOKEN_VALUE() do { value = strchr (token, '=') + 1; if (*value == '"') value++; } while (0)
  936.                   if (false) {}
  937.                   else if (strncmp (token, "prefix=",  7) == 0) { REACH_TOKEN_VALUE (); entry_parms->prefix  = (*value == '/' ? value + 1 : value); } // skip possible leading slash in prefix (NOTE: stolen pointer. Do not free.)
  938.                   else if (strncmp (token, "uid=",     4) == 0) { REACH_TOKEN_VALUE (); entry_parms->uid     = (int) read_integer (value, 10); }
  939.                   else if (strncmp (token, "gid=",     4) == 0) { REACH_TOKEN_VALUE (); entry_parms->gid     = (int) read_integer (value, 10); }
  940.                   else if (strncmp (token, "perms=",   6) == 0) { REACH_TOKEN_VALUE (); entry_parms->perms   = (int) read_integer (value, 8); }
  941.                   else if (strcmp (token, "+followlink") == 0) entry_parms->should_follow_symlinks = true;
  942.                   else if (strcmp (token, "-followlink") == 0) entry_parms->should_follow_symlinks = false;
  943.                   else if (strcmp (token, "+keeplinked") == 0) entry_parms->should_keep_ld_output = true;
  944.                   else if (strcmp (token, "-keeplinked") == 0) entry_parms->should_keep_ld_output = false;
  945.                   else LOG_WARNING ("unimplemented bootstrap executable attribute in \"%s\" line %zd of inline document '%s': '%s'", buildfile_pathname, 1 + line_index, stored_pathname, token);
  946.                   #undef REACH_TOKEN_VALUE
  947.                   token = strtok_r (NULL, RECORD_SEP, &ctx); // proceed to next attribute token
  948.                }
  949.  
  950.                if (is_end_of_line)
  951.                   continue; // if end of line was reached, proceed to the next line
  952.                else
  953.                   read_ptr++; // else reach the next character (after the NUL split) and continue processing the same line
  954.             } // end of "this line starts with an attributes block"
  955.  
  956.             // at this point we are past the attributes block
  957.  
  958.             // reset contextual argv/envp arrays
  959.             line_argv.args = NULL;
  960.             line_argv.count = 0;
  961.             line_envp.args = NULL;
  962.             line_envp.count = 0;
  963.  
  964.             // now read each word (or quoted group of words), unescaping escaped characters
  965.             while (*read_ptr != 0)
  966.             {
  967.                while ((*read_ptr != 0) && isspace (*read_ptr))
  968.                   read_ptr++; // skip intermediate spaces and reach the next word
  969.  
  970.                if (*read_ptr == '#')
  971.                   break; // if the rest of the line is commented out, stop parsing it and proceed to the next line
  972.  
  973.                linebit_start = read_ptr; // remember the word (or quoted group of words) starts here
  974.                write_ptr = read_ptr;
  975.                is_quoted_context = (*read_ptr == '"'); // see if we're entering a quoted context or not
  976.                if (is_quoted_context)
  977.                   read_ptr++; // skip a possible initial quote in the word
  978.                while ((*read_ptr != 0) && ((!is_quoted_context && !isspace (*read_ptr)) || (is_quoted_context && (*read_ptr != '"'))))
  979.                {
  980.                   if (*read_ptr == '\\')
  981.                      read_ptr++; // unescape characters that are escaped with '\' by advancing the read pointer
  982.                   *write_ptr++ = *read_ptr++; // recopy characters as we read them
  983.                }
  984.                is_end_of_line = (*read_ptr == 0); // see if we're at the end of line already
  985.                *write_ptr = 0; // stop the rewritten string here
  986.  
  987.                // end of word, i.e. we reached either a closing quote or a space. The string bit has been rewritted at linebit_start without quotes and with characters unescaped.
  988.  
  989.                if ((strchr (linebit_start, '=') != NULL) && (line_argv.count == 0)) // is it an assignation AND have we not started constructing argv yet?
  990.                {
  991.                   STRINGARRAY_PUSH (&line_envp, linebit_start); // linebit_start is of the form "NAME=VALUE": it's an environment variable assignation
  992.                   LOG_DEBUG ("collected envp: [%s]", linebit_start);
  993.                }
  994.                else // it's an executable argument (argv)
  995.                {
  996.                   STRINGARRAY_PUSH (&line_argv, linebit_start); // linebit_start is either NOT of the form "NAME=VALUE" OR we started constructing argv: it's a command-line argument
  997.                   LOG_DEBUG ("collected argv: [%s]", linebit_start);
  998.                }
  999.  
  1000.                if (!is_end_of_line)
  1001.                   read_ptr++; // if we haven't reach the end of the line yet, advance to the next character (after the NUL split)
  1002.             } // end while (*read_ptr != 0)
  1003.  
  1004.             // we finished parsing the line
  1005.  
  1006.             // did we fill an executable argv? As per QNX docs, the first executable must be startup-*, the last executable must be procnto.
  1007.             if (line_argv.count > 0)
  1008.             {
  1009.                if (startup_argv.args == NULL)
  1010.                {
  1011.                   startup_argv.args = line_argv.args; // relocate these pointers to the right place
  1012.                   startup_argv.count = line_argv.count;
  1013.                   startup_envp.args = line_envp.args; // relocate these pointers to the right place
  1014.                   startup_envp.count = line_envp.count;
  1015.                }
  1016.                else
  1017.                {
  1018.                   STRINGARRAY_FREE (&procnto_argv); // if procnto's argv was already assigned, free the previous array as we'll be replacing it with a new one
  1019.                   procnto_argv.args = line_argv.args; // relocate these pointers to the right place
  1020.                   procnto_argv.count = line_argv.count;
  1021.                   STRINGARRAY_FREE (&procnto_envp); // if procnto's envp was already assigned, free the previous array as we'll be replacing it with a new one
  1022.                   procnto_envp.args = line_envp.args; // relocate these pointers to the right place
  1023.                   procnto_envp.count = line_envp.count;
  1024.                }
  1025.                line_argv.args = NULL; // void the line_argv array so as to not free it as we stole its args pointers
  1026.                line_argv.count = 0;
  1027.                line_envp.args = NULL; // void the line_envp array so as to not free it as we stole its args pointers
  1028.                line_envp.count = 0;
  1029.             }
  1030.             else // this line contained no executable invokation, so stack up its envp assignations into the global envp array
  1031.                for (array_index = 0; array_index < line_envp.count; array_index++)
  1032.                   STRINGARRAY_PUSH (&global_envp, line_envp.args[array_index]);
  1033.  
  1034.             // release the contextual argv/envp arrays
  1035.             STRINGARRAY_FREE (&line_argv);
  1036.             STRINGARRAY_FREE (&line_envp);
  1037.  
  1038.          } // end for (line_index = 0; Buffer_GetNthLine (&entry_parms->data, line_index, &current_line); line_index++)
  1039.          Buffer_Forget (&entry_parms->data); // free the inline specification once it's parsed
  1040.  
  1041.          ASSERT (startup_argv.args && startup_argv.args[0] && *startup_argv.args[0], "the QNX startup executable (startup-*) is missing in this bootstrap inline specification");
  1042.          ASSERT (procnto_argv.args && procnto_argv.args[0] && *procnto_argv.args[0], "the QNX kernel (procnto-*) is missing in this bootstrap inline specification");
  1043.  
  1044.          // now we know which startup and procnto executables to use
  1045.          LOG_DEBUG ("Startup: %s", startup_argv.args[0]);
  1046.          LOG_DEBUG ("Kernel: %s",  procnto_argv.args[0]);
  1047.  
  1048.          static thread_local char linker_pathname[MAXPATHLEN] = "";
  1049.          static thread_local char linker_sysroot_arg[MAXPATHLEN] = "";
  1050.          static thread_local char linker_script_pathname_arg[MAXPATHLEN] = "";
  1051.          static thread_local char procnto_buildhost_pathname[MAXPATHLEN] = "";
  1052.          static thread_local char procnto_sym_filename[MAXPATHLEN] = "";
  1053.          buffer_t bootargs_buffer = { 0 };
  1054.          char *bootargs_location;
  1055.  
  1056.          // construct the arguments that are based on environment variables (infer QNX_HOST from QNX_TARGET)
  1057. #if defined(_WIN32)
  1058.          sprintf_s (linker_pathname, sizeof (linker_pathname), "%s/../../host/win64/x86_64/usr/bin/%s-ld" /*"-2.41.0"*/ ".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
  1059. #elif defined(__linux__)
  1060.          sprintf_s (linker_pathname, sizeof (linker_pathname), "%s/../../host/linux/x86_64/usr/bin/%s-ld" /*"-2.41.0"*/, QNX_TARGET, (strcmp (image_processor, "x86_64") == 0 ? "x86_64-pc-nto-qnx8.0.0" : "aarch64-unknown-nto-qnx8.0.0"));
  1061. #elif defined(__QNXNTO__)
  1062.          sprintf_s (linker_pathname, sizeof (linker_pathname), "%s/../../host/qnx8/x86_64/usr/bin/%s-ld" /*"-2.41.0"*/, QNX_TARGET, (strcmp (image_processor, "x86_64") == 0 ? "x86_64-pc-nto-qnx8.0.0" : "aarch64-unknown-nto-qnx8.0.0"));
  1063. #else // wtf are you building this on?
  1064. #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.
  1065. #endif
  1066.          ASSERT (access (linker_pathname, 0) == 0, "host cross-linker for QNX8 \"%s\" not found", linker_pathname);
  1067.          sprintf_s (linker_sysroot_arg, sizeof (linker_sysroot_arg), "--sysroot=%s/%s/", QNX_TARGET, image_processor);
  1068.          sprintf_s (linker_script_pathname_arg, sizeof (linker_script_pathname_arg), "-T%s/%s/lib/nto.link", QNX_TARGET, image_processor);
  1069.  
  1070.          resolved_pathname = resolve_pathname (procnto_argv.args[0], entry_parms->search); // locate the procnto kernel location
  1071.          ASSERT (resolved_pathname, "QNX kernel \"%s\" not found in search path", procnto_argv.args[0]);
  1072.          strcpy_s (procnto_buildhost_pathname, sizeof (procnto_buildhost_pathname), resolved_pathname);
  1073.  
  1074.          sprintf_s (procnto_sym_filename, sizeof (procnto_sym_filename), "%s.sym%s", procnto_argv.args[0], sym_suffix);
  1075.  
  1076.          // construct the linker invokation command-line arguments array (argv)
  1077.          STRINGARRAY_INIT (&linker_argv);
  1078.          STRINGARRAY_PUSH (&linker_argv, strrchr (linker_pathname, '/') + 1); // "${TARGET_TRIPLE}-ld"
  1079.          STRINGARRAY_PUSH (&linker_argv, linker_sysroot_arg); // "--sysroot=${QNX_TARGET}/${TARGET_CPU}/"
  1080.          STRINGARRAY_PUSH (&linker_argv, linker_script_pathname_arg); // "-T${QNX_TARGET}/${TARGET_CPU}/lib/nto.link"
  1081.          STRINGARRAY_PUSH (&linker_argv, "--section-start");
  1082.          STRINGARRAY_PUSH (&linker_argv, (boot_type == BOOTTYPE_UEFI ? ".text=0xffff800000002000" : ".text=0xffff800000001000")); // FIXME: wild assumption!
  1083.          STRINGARRAY_PUSH (&linker_argv, "--no-relax");
  1084.          STRINGARRAY_PUSH (&linker_argv, procnto_buildhost_pathname); // "${QNX_TARGET}/${TARGET_CPU}/boot/sys/procnto-smp-instr"
  1085.          STRINGARRAY_PUSH (&linker_argv, "-o");
  1086.          STRINGARRAY_PUSH (&linker_argv, procnto_sym_filename); // "procnto-smp-instr.sym"
  1087. #ifdef __GNUC__
  1088. #pragma GCC diagnostic push
  1089. #pragma GCC diagnostic ignored "-Wnonnull" // the GCC linter is wrong here: I *do* check for NULL before calling strdup()
  1090. #endif // __GNUC__
  1091.          STRINGARRAY_PUSH (&linker_argv, NULL); // don't forget to terminate the argv array with a NULL pointer
  1092. #ifdef __GNUC__
  1093. #pragma GCC diagnostic pop
  1094. #endif // __GNUC__
  1095.          if (verbose_level > 2)
  1096.          {
  1097.             fprintf (stderr, "ifstool: calling:");
  1098.             for (array_index = 0; array_index < linker_argv.count - 1; array_index++)
  1099.                fprintf (stderr, " '%s'", linker_argv.args[array_index]);
  1100.             fputc ('\n', stderr);
  1101.          }
  1102. #ifdef _WIN32
  1103.          _spawnv (_P_WAIT, linker_pathname, linker_argv.args); // spawn the linker and produce a stripped procnto (wait for completion)
  1104. #else // !_WIN32, thus POSIX
  1105.          do {
  1106.             int status;
  1107.             pid_t pid = fork (); // duplicate ourselves so as to create a new process
  1108.             ASSERT_WITH_ERRNO (pid != -1);
  1109.             if (pid == 0) // we are the child
  1110.             {
  1111.                execv (linker_pathname, linker_argv.args); // execute the linker and produce a stripped procnto (wait for completion)
  1112.                DIE_WITH_EXITCODE (1, "execve() failed"); // exec never returns
  1113.             }
  1114.             else // we are the parent
  1115.                waitpid (pid, &status, 0); // wait for the child to finish
  1116.          } while (0);
  1117. #endif // _WIN32
  1118.          STRINGARRAY_FREE (&linker_argv);
  1119.          if (!Buffer_ReadFromFile (&entry_parms->data, procnto_sym_filename)) // load the output file
  1120.             DIE_WITH_EXITCODE (1, "the host cross-linker failed to produce a readable stripped \"%s\" kernel: %s", procnto_sym_filename, strerror (errno));
  1121.          if (!entry_parms->should_keep_ld_output)
  1122.             unlink (procnto_sym_filename); // remove the linker output file if we want to
  1123.  
  1124.          // save the boot arguments. The magic to look for is "ddpvbskr" -- whatever that means
  1125.          if ((bootargs_location = Buffer_FindFirstByteArray (&entry_parms->data, "ddpvbskr")) == NULL)
  1126.             DIE_WITH_EXITCODE (1, "unable to find boot args location in the stripped \"%s\" kernel", stored_pathname);
  1127.          Buffer_InitWithSize (&bootargs_buffer, sizeof (bootargs_entry_t)); // prepare a boot args entry
  1128.          ((bootargs_entry_t *) bootargs_buffer.bytes)->argc = (uint8_t) procnto_argv.count;
  1129.          ((bootargs_entry_t *) bootargs_buffer.bytes)->envc = (uint8_t) (global_envp.count + procnto_envp.count);
  1130.          ((bootargs_entry_t *) bootargs_buffer.bytes)->shdr_addr = (uint32_t) (image_base + bootfile_size); // same value as startup_header.image_paddr (which is not set yet) (TODO: support 64-bit shdr_addr offsets -- see comment in bootargs_entry_t struct)
  1131.          for (array_index = 0; array_index < procnto_argv.count; array_index++)
  1132.             ASSERT_WITH_ERRNO (Buffer_Append (&bootargs_buffer, procnto_argv.args[array_index], strlen (procnto_argv.args[array_index]) + 1)); // append string including NUL terminator
  1133.          for (array_index = 0; array_index < global_envp.count; array_index++)
  1134.             ASSERT_WITH_ERRNO (Buffer_Append (&bootargs_buffer, global_envp.args[array_index], strlen (global_envp.args[array_index]) + 1)); // append string including NUL terminator
  1135.          for (array_index = 0; array_index < procnto_envp.count; array_index++)
  1136.             ASSERT_WITH_ERRNO (Buffer_Append (&bootargs_buffer, procnto_envp.args[array_index], strlen (procnto_envp.args[array_index]) + 1)); // append string including NUL terminator
  1137.          ((bootargs_entry_t *) bootargs_buffer.bytes)->size_hi = (uint8_t) ((bootargs_buffer.size >> 8) & 0xff);
  1138.          ((bootargs_entry_t *) bootargs_buffer.bytes)->size_lo = (uint8_t) ((bootargs_buffer.size >> 0) & 0xff);
  1139.          ASSERT_WITH_ERRNO (Buffer_WriteBufferAt (&entry_parms->data, (size_t) bootargs_location - (size_t) entry_parms->data.bytes, &bootargs_buffer));
  1140.          Buffer_Forget (&bootargs_buffer); // release the boot args buffer once it's written
  1141.  
  1142.          // now strip this prelinked ELF kernel file
  1143.          ASSERT_WITH_ERRNO (Buffer_StripELFFile (&entry_parms->data, (const char **) saved_ELF_sections, 1, true, stored_pathname)); // strip the ELF file as per QNX docs (only keep ONE section, which is "QNX_info", and align the segment size in file with the size it occupies in memory)
  1144.  
  1145.          sprintf_s (candidate_pathname, MAXPATHLEN, "%s/%s", (entry_parms->prefix != NULL ? entry_parms->prefix : ""), procnto_argv.args[0]); // fix the entry name
  1146.          stored_pathname = candidate_pathname;
  1147.  
  1148.          entry_parms->extra_ino_flags |= IFS_INO_PROCESSED_ELF | IFS_INO_BOOTSTRAP_EXE; // mark this inode as a preprocessed *bootstrap* ELF file
  1149.          entry_parms->st_mode = S_IFREG | entry_parms->perms; // procnto is a regular file
  1150.          image_kernel_ino = entry_parms->extra_ino_flags | (inode_count + 1);
  1151.  
  1152.          STRINGARRAY_FREE (&procnto_argv); // release procnto's argv array
  1153.          STRINGARRAY_FREE (&procnto_envp); // release procnto's envp array
  1154.          //STRINGARRAY_FREE (&global_envp); // DO NOT release the global envp array. It is inherited by the boot scripts.
  1155.       } // end of "is bootstrap file"
  1156.       else if (entry_parms->is_compiled_bootscript) // else is it a startup script that we need to compile ?
  1157.       {
  1158.          image_bootscript_ino = inode_count + 1; // save boot script inode number for image header
  1159.          Buffer_Initialize (&compiled_script);
  1160.  
  1161.          // parse buffer (non-destructively) line after line
  1162.          Buffer_Initialize (&current_line);
  1163.          for (line_index = 0; Buffer_GetNthLine (&entry_parms->data, line_index, &current_line); line_index++)
  1164.          {
  1165.             read_ptr = current_line.bytes;
  1166.             while (isspace (*read_ptr))
  1167.                read_ptr++; // skip leading spaces
  1168.             if ((*read_ptr == '#') || (*read_ptr == 0))
  1169.                continue; // skip comments and empty lines
  1170.  
  1171.             // format of a line: [attributes] [env assignation] [...] [executable] [arg] [...] [&] [comment]
  1172.             // example: "[pri=20f] devc-con -n9 &"
  1173.  
  1174.             LOG_DEBUG ("parsing line: %s", read_ptr);
  1175.             Buffer_Initialize (&compiled_scriptline);
  1176.             memcpy (&current_scriptcmd_params, &default_scriptcmd_params, sizeof (default_scriptcmd_params));
  1177.  
  1178.             // does this line start with an attribute block ?
  1179.             if (*read_ptr == '[')
  1180.             {
  1181.                read_ptr++; // skip the leading square bracket
  1182.                linebit_start = read_ptr; // remember where it starts
  1183.                is_quoted_context = false; // reach the next unescaped closing square bracket that is not between quotes
  1184.                while ((*read_ptr != 0) && !((*read_ptr == ']') && (read_ptr[-1] != '\\') && !is_quoted_context))
  1185.                {
  1186.                   if (*read_ptr == '"')
  1187.                      is_quoted_context ^= true; // remember when we're between quotes
  1188.                   else if (!is_quoted_context && (*read_ptr == ' '))
  1189.                      *read_ptr = RECORD_SEP[0]; // turn all spaces outside quoted contexts into an ASCII record separator to ease token splitting
  1190.                   read_ptr++; // reach the next unescaped closing square bracket
  1191.                }
  1192.                if (*read_ptr != ']')
  1193.                {
  1194.                   LOG ("warning", 0, "syntax error in \"%s\" line %zd of inline document '%s': unterminated attributes block (skipping)", buildfile_pathname, 1 + line_index, stored_pathname);
  1195.                   continue; // invalid attribute block, skip line
  1196.                }
  1197.                is_end_of_line = (*read_ptr == 0); // see if we're at the end of line already
  1198.                *read_ptr = 0; // end the attribute block in all cases so that it is a parsable C string
  1199.  
  1200.                // now parse the attribute tokens
  1201.                token = strtok_r (linebit_start, RECORD_SEP, &ctx);
  1202.                while (token != NULL)
  1203.                {
  1204.                   #define REACH_TOKEN_VALUE() do { value = strchr (token, '=') + 1; if (*value == '"') value++; } while (0)
  1205.                   if (false) {}
  1206.                   else if (strncmp (token, "argv0=",      6) == 0) { REACH_TOKEN_VALUE (); current_scriptcmd_params.argv0      = value; } // NOTE: stolen pointer. Do not free.
  1207.                   else if (strncmp (token, "cpu=",        4) == 0) { REACH_TOKEN_VALUE (); current_scriptcmd_params.cpu_number = (int) atoi (value); }
  1208.                   else if (strncmp (token, "pri=",        4) == 0) { REACH_TOKEN_VALUE (); current_scriptcmd_params.priority   = (int) strtol (value, &ctx, 0); if (ctx != NULL) current_scriptcmd_params.sched_policy = (*ctx == 'f' ? SCRIPTCMD_SCHEDULERPOLICY_FIFO : SCRIPTCMD_SCHEDULERPOLICY_RR); }
  1209.                   else if (strncmp (token, "sched_aps=", 10) == 0) { REACH_TOKEN_VALUE ();
  1210.                      for (array_index = 0; array_index < aps_partnames.count; array_index++) if (strcmp (aps_partnames.args[array_index], value) == 0) break;
  1211.                      if (array_index == aps_partnames.count)
  1212.                         DIE_WITH_EXITCODE (1, "syntax error in \"%s\" line %zd of inline document '%s': APS partition name '%s' not found: please declare it first", buildfile_pathname, 1 + line_index, stored_pathname, value); // consistency check (TODO: check that the sum of all budgets don't exceed 100%)
  1213.                      current_scriptcmd_params.aps_partindex = (int) array_index;
  1214.                   }
  1215.                   else if (strcmp (token, "+external") == 0) current_scriptcmd_params.is_external = true;
  1216.                   else if (strcmp (token, "-external") == 0) current_scriptcmd_params.is_external = false;
  1217.                   else if (strcmp (token, "+session")  == 0) current_scriptcmd_params.is_session_leader = true;
  1218.                   else if (strcmp (token, "-session")  == 0) current_scriptcmd_params.is_session_leader = false;
  1219.                   else if (strcmp (token, "+debug")    == 0) current_scriptcmd_params.has_debug_flag = true;
  1220.                   else if (strcmp (token, "-debug")    == 0) current_scriptcmd_params.has_debug_flag = false;
  1221.                   else LOG_WARNING ("unimplemented boot script modifier in \"%s\" line %zd of inline document '%s': '%s'", buildfile_pathname, 1 + line_index, stored_pathname, token);
  1222.                   #undef REACH_TOKEN_VALUE
  1223.                   token = strtok_r (NULL, RECORD_SEP, &ctx); // proceed to next attribute token
  1224.                }
  1225.  
  1226.                if (is_end_of_line)
  1227.                   continue; // if end of line was reached, proceed to the next line
  1228.                else
  1229.                   read_ptr++; // else reach the next character (after the NUL split) and continue processing the same line
  1230.             } // end of "this line starts with an attributes block"
  1231.  
  1232.             // at this point we are past the attributes block
  1233.  
  1234.             // reset contextual argv/envp arrays
  1235.             line_argv.args = NULL;
  1236.             line_argv.count = 0;
  1237.             line_envp.args = NULL;
  1238.             line_envp.count = 0;
  1239.  
  1240.             // now read each word (or quoted group of words), unescaping escaped characters
  1241.             while (*read_ptr != 0)
  1242.             {
  1243.                while ((*read_ptr != 0) && isspace (*read_ptr))
  1244.                   read_ptr++; // skip intermediate spaces and reach the next word
  1245.  
  1246.                if (*read_ptr == '#')
  1247.                   break; // if the rest of the line is commented out, stop parsing it and proceed to the next line
  1248.  
  1249.                linebit_start = read_ptr; // remember the word (or quoted group of words) starts here
  1250.                write_ptr = read_ptr;
  1251.                is_quoted_context = (*read_ptr == '"'); // see if we're entering a quoted context or not
  1252.                if (is_quoted_context)
  1253.                   read_ptr++; // skip a possible initial quote in the word
  1254.                while ((*read_ptr != 0) && ((!is_quoted_context && !isspace (*read_ptr)) || (is_quoted_context && (*read_ptr != '"'))))
  1255.                {
  1256.                   if (*read_ptr == '\\')
  1257.                      read_ptr++; // unescape characters that are escaped with '\' by advancing the read pointer
  1258.                   *write_ptr++ = *read_ptr++; // recopy characters as we read them
  1259.                }
  1260.                is_end_of_line = (*read_ptr == 0); // see if we're at the end of line already
  1261.                *write_ptr = 0; // stop the rewritten string here
  1262.  
  1263.                // end of word, i.e. we reached either a closing quote or a space. The string bit has been rewritted at linebit_start without quotes and with characters unescaped.
  1264.                STRINGARRAY_PUSH (&line_argv, linebit_start);
  1265.                LOG_DEBUG ("collected bootscript argv: [%s]", linebit_start);
  1266.  
  1267.                if (!is_end_of_line)
  1268.                   read_ptr++; // if we haven't reach the end of the line yet, advance to the next character (after the NUL split)
  1269.             } // end while (*read_ptr != 0)
  1270.  
  1271.             // we finished parsing the line
  1272.  
  1273.             // did we fill an executable argv? As per QNX docs, the first executable must be startup-*, the last executable must be procnto.
  1274.             if (line_argv.count > 0)
  1275.             {
  1276.                // is it one of the few builtin commands ?
  1277.                if (!current_scriptcmd_params.is_external && (strcmp (line_argv.args[0], "waitfor") == 0))
  1278.                {
  1279.                   if (line_argv.count < 2)
  1280.                      DIE_WITH_EXITCODE (1, "syntax error in \"%s\" line %zd of inline document '%s': waitfor requires 1 or 2 arguments", buildfile_pathname, 1 + line_index, stored_pathname);
  1281.  
  1282.                   ASSERT_WITH_ERRNO (Buffer_InitWithData (&compiled_scriptline, "##" SCRIPTCMD_TYPE_WAITFOR "\x00", 4)); // size as u16LE, type, spare
  1283.                   wait_time = (line_argv.count > 2 ? (size_t) (10.0 * atof (line_argv.args[2])) : 50); // convert dotted number to tenths of seconds. Default to 5 seconds (50 tenths)
  1284.                   if (wait_time > 0xffff)
  1285.                      wait_time = 0xffff;
  1286.                   ASSERT_WITH_ERRNO (Buffer_WriteInt8At (&compiled_scriptline, 4, (wait_time >> 0) & 0xff)); // wait time lo
  1287.                   ASSERT_WITH_ERRNO (Buffer_WriteInt8At (&compiled_scriptline, 5, (wait_time >> 8) & 0xff)); // wait time hi
  1288.                   ASSERT_WITH_ERRNO (Buffer_Append (&compiled_scriptline, line_argv.args[1], strlen (line_argv.args[1]) + 1));
  1289.                }
  1290.                else if (!current_scriptcmd_params.is_external && (strcmp (line_argv.args[0], "reopen") == 0))
  1291.                {
  1292.                   ASSERT_WITH_ERRNO (Buffer_InitWithData (&compiled_scriptline, "##" SCRIPTCMD_TYPE_REOPEN "\x00", 4)); // size as u16LE, type, spare
  1293.                   wait_time = (line_argv.count > 2 ? (size_t) (10.0 * atof (line_argv.args[2])) : 50); // convert dotted number to tenths of seconds. Default to 5 seconds (50 tenths)
  1294.                   if (wait_time > 0xffff)
  1295.                      wait_time = 0xffff;
  1296.                   ASSERT_WITH_ERRNO (Buffer_WriteInt8At (&compiled_scriptline, 4, (wait_time >> 0) & 0xff)); // wait time lo
  1297.                   ASSERT_WITH_ERRNO (Buffer_WriteInt8At (&compiled_scriptline, 5, (wait_time >> 8) & 0xff)); // wait time hi
  1298.                   ASSERT_WITH_ERRNO (Buffer_Append (&compiled_scriptline, (line_argv.count > 1 ? line_argv.args[1] : "/dev/console"), strlen (line_argv.count > 1 ? line_argv.args[1] : "/dev/console") + 1));
  1299.                }
  1300.                else if (!current_scriptcmd_params.is_external && (strcmp (line_argv.args[0], "display_msg") == 0))
  1301.                {
  1302.                   ASSERT_WITH_ERRNO (Buffer_InitWithData (&compiled_scriptline, "##" SCRIPTCMD_TYPE_DISPLAY_MSG "\x00", 4)); // size as u16LE, type, spare
  1303.                   for (array_index = 1; array_index < line_argv.count; array_index++)
  1304.                   {
  1305.                      if (array_index > 1)
  1306.                         ASSERT_WITH_ERRNO (Buffer_AppendByteArray (&compiled_scriptline, " ")); // separate each arg with a space
  1307.                      ASSERT_WITH_ERRNO (Buffer_Append (&compiled_scriptline, line_argv.args[array_index], strlen (line_argv.args[array_index])));
  1308.                   }
  1309.                   ASSERT_WITH_ERRNO (Buffer_AppendByteArray (&compiled_scriptline, "\n\0")); // don't forget to append a newline to the message printed
  1310.                }
  1311.                else if (!current_scriptcmd_params.is_external && (strcmp (line_argv.args[0], "procmgr_symlink") == 0))
  1312.                {
  1313.                   if (line_argv.count < 3)
  1314.                      DIE_WITH_EXITCODE (1, "syntax error in \"%s\" line %zd of inline document '%s': procmgr_symlink requires 2 arguments", buildfile_pathname, 1 + line_index, stored_pathname);
  1315.  
  1316.                   ASSERT_WITH_ERRNO (Buffer_InitWithData (&compiled_scriptline, "##" SCRIPTCMD_TYPE_PROCMGR_SYMLINK "\x00", 4)); // size as u16LE, type, spare
  1317.                   ASSERT_WITH_ERRNO (Buffer_Append (&compiled_scriptline, line_argv.args[1], strlen (line_argv.args[1]) + 1));
  1318.                   ASSERT_WITH_ERRNO (Buffer_Append (&compiled_scriptline, line_argv.args[2], strlen (line_argv.args[2]) + 1));
  1319.                }
  1320.                else if (!current_scriptcmd_params.is_external && (strcmp (line_argv.args[0], "sched_aps") == 0))
  1321.                {
  1322.                   token = (line_argv.count > 1 ? line_argv.args[1] : "System");
  1323.                   if ((strlen (token) > 15) || (strchr (token, '/') != NULL))
  1324.                      DIE_WITH_EXITCODE (1, "syntax error in \"%s\" line %zd of inline document '%s': APS partition names must be less than 16 characters long and not contain a '/' separator", buildfile_pathname, 1 + line_index, stored_pathname); // consistency check (TODO: check that the sum of all budgets don't exceed 100%)
  1325.                   for (array_index = 0; array_index < aps_partnames.count; array_index++)
  1326.                      if (strcmp (aps_partnames.args[array_index], token) == 0)
  1327.                         break; // find the APS partition ID in the global APS partition names table
  1328.                   if (array_index == aps_partnames.count)
  1329.                      STRINGARRAY_PUSH (&aps_partnames, token); // if not found, add a new partition name to the table
  1330.                   ASSERT_WITH_ERRNO (Buffer_InitWithData (&compiled_scriptline, "##" SCRIPTCMD_TYPE_EXTSCHED_APS "\x00", 4)); // size as u16LE, type, spare
  1331.                   ASSERT_WITH_ERRNO (Buffer_WriteInt8At (&compiled_scriptline, 4, 0)); // parent (system partition)
  1332.                   ASSERT_WITH_ERRNO (Buffer_WriteInt8At (&compiled_scriptline, 5, (line_argv.count > 2 ? (uint8_t) atoi (line_argv.args[2]) : 0))); // budget
  1333.                   ASSERT_WITH_ERRNO (Buffer_WriteInt8At (&compiled_scriptline, 6, ((line_argv.count > 3 ? (uint8_t) atoi (line_argv.args[3]) : 0) >> 0) & 0xff)); // critical lo
  1334.                   ASSERT_WITH_ERRNO (Buffer_WriteInt8At (&compiled_scriptline, 7, ((line_argv.count > 3 ? (uint8_t) atoi (line_argv.args[3]) : 0) >> 8) & 0xff)); // critical hi
  1335.                   ASSERT_WITH_ERRNO (Buffer_WriteInt8At (&compiled_scriptline, 8, (uint8_t) array_index)); // APS partition ID
  1336.                   ASSERT_WITH_ERRNO (Buffer_Append (&compiled_scriptline, token, strlen (token) + 1)); // partition name
  1337.                }
  1338.                else // not a builtin, which means it is an external command
  1339.                {
  1340.                   if (strcmp (line_argv.args[line_argv.count - 1], "&") == 0) // is the last argument an ampersand (fork sign) on its own ? (variant 1)
  1341.                   {
  1342.                      current_scriptcmd_params.is_background_task = true; // remember this is a background task
  1343.                      free (line_argv.args[line_argv.count - 1]); // prevent leaking the last arg
  1344.                      line_argv.count--; // and adjust the arg count
  1345.                   }
  1346.                   else if (((token = strrchr (line_argv.args[line_argv.count - 1], '&')) != NULL) && (token[1] == 0)) // else does the last argument END with a fork sign ? (variant 2)
  1347.                   {
  1348.                      current_scriptcmd_params.is_background_task = true; // remember this is a background task
  1349.                      *token = 0; // and chop off the ampersand from that arg
  1350.                   }
  1351.  
  1352.                   ASSERT_WITH_ERRNO (Buffer_InitWithData (&compiled_scriptline, "##" SCRIPTCMD_TYPE_EXTERNAL "\x00", 4)); // size as u16LE, type, spare
  1353.                   ASSERT_WITH_ERRNO (Buffer_WriteInt8At (&compiled_scriptline, 4, (current_scriptcmd_params.cpu_number != -1 ? (uint8_t) current_scriptcmd_params.cpu_number : 0))); // CPU
  1354.                   ASSERT_WITH_ERRNO (Buffer_WriteInt8At (&compiled_scriptline, 5, (current_scriptcmd_params.aps_partindex != -1 ? SCRIPTCMD_FLAG_EXTSCHED   : 0)
  1355.                                                                                 | (current_scriptcmd_params.is_session_leader   ? SCRIPTCMD_FLAG_SESSION    : 0)
  1356.                                                                                 | (current_scriptcmd_params.sched_policy  != -1 ? SCRIPTCMD_FLAG_SCHED_SET  : 0)
  1357.                                                                                 | (current_scriptcmd_params.cpu_number    != -1 ? SCRIPTCMD_FLAG_CPU_SET    : 0)
  1358.                                                                                 | (current_scriptcmd_params.is_background_task  ? SCRIPTCMD_FLAG_BACKGROUND : 0)
  1359.                                                                                 | (current_scriptcmd_params.has_debug_flag      ? SCRIPTCMD_FLAG_KDEBUG     : 0))); // flags
  1360.                   ASSERT_WITH_ERRNO (Buffer_WriteInt8At (&compiled_scriptline, 6, (current_scriptcmd_params.aps_partindex != -1 ? (uint8_t) current_scriptcmd_params.aps_partindex : 0))); // adaptative partitioning ID
  1361.                   ASSERT_WITH_ERRNO (Buffer_WriteInt8At (&compiled_scriptline, 7, 0)); // reserved
  1362.                   ASSERT_WITH_ERRNO (Buffer_WriteInt8At (&compiled_scriptline, 8, (current_scriptcmd_params.sched_policy != -1 ? current_scriptcmd_params.sched_policy : 0))); // scheduling policy
  1363.                   ASSERT_WITH_ERRNO (Buffer_WriteInt8At (&compiled_scriptline, 9, (current_scriptcmd_params.priority != -1 ? current_scriptcmd_params.priority : 0))); // scheduling priority
  1364.                   ASSERT_WITH_ERRNO (Buffer_WriteInt8At (&compiled_scriptline, 10, (uint8_t) line_argv.count)); // argc
  1365.                   ASSERT_WITH_ERRNO (Buffer_WriteInt8At (&compiled_scriptline, 11, (uint8_t) global_envp.count)); // envc
  1366.                   ASSERT_WITH_ERRNO (Buffer_Append (&compiled_scriptline, line_argv.args[0], strlen (line_argv.args[0]) + 1)); // executable
  1367.                   if (current_scriptcmd_params.argv0 != NULL)
  1368.                      ASSERT_WITH_ERRNO (Buffer_Append (&compiled_scriptline, current_scriptcmd_params.argv0, strlen (current_scriptcmd_params.argv0) + 1)); // argv[0] -- explicit value from attribute
  1369.                   else
  1370.                   {
  1371.                      filename_bit = strrchr (line_argv.args[0], '/'); // argv[0] has an implicit value: look where the filename starts
  1372.                      filename_bit = (filename_bit != NULL ? filename_bit + 1 : line_argv.args[0]);
  1373.                      ASSERT_WITH_ERRNO (Buffer_Append (&compiled_scriptline, filename_bit, strlen (filename_bit) + 1)); // argv[0] -- store just the filename
  1374.                   }
  1375.                   for (array_index = 1; array_index < line_argv.count; array_index++)
  1376.                      ASSERT_WITH_ERRNO (Buffer_Append (&compiled_scriptline, line_argv.args[array_index], strlen (line_argv.args[array_index]) + 1)); // argv[n]
  1377.                   for (array_index = 0; array_index < global_envp.count; array_index++)
  1378.                      ASSERT_WITH_ERRNO (Buffer_Append (&compiled_scriptline, global_envp.args[array_index], strlen (global_envp.args[array_index]) + 1)); // envp[n]
  1379.                }
  1380.                ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (&compiled_scriptline, ROUND_TO_UPPER_MULTIPLE (compiled_scriptline.size, 4))); // pad compiled command buffer to upper 32-bit multiple
  1381.  
  1382.                // fix the size of this compiled boot script command
  1383.                ASSERT_WITH_ERRNO (Buffer_WriteInt8At (&compiled_scriptline, 0, (compiled_scriptline.size >> 0) & 0xff)); // size lo
  1384.                ASSERT_WITH_ERRNO (Buffer_WriteInt8At (&compiled_scriptline, 1, (compiled_scriptline.size >> 8) & 0xff)); // size hi
  1385.  
  1386.                // now concatenate this newly compiled boot script line to the compiled boot script buffer
  1387.                ASSERT_WITH_ERRNO (Buffer_AppendBuffer (&compiled_script, &compiled_scriptline));
  1388.                Buffer_Forget (&compiled_scriptline);
  1389.             }
  1390.             else // this line contained no executable invokation, so make the parameters that changed the default ones
  1391.             {
  1392.                #define APPLY_DEFAULT_ATTR_NUM(attr,descr,fmt) do { if (current_scriptcmd_params.attr != default_scriptcmd_params.attr) { \
  1393.                      LOG_INFO ("changing default " descr " from " fmt " to " fmt " by attribute at \"%s\" line %zd of inline document '%s'", default_scriptcmd_params.attr, current_scriptcmd_params.attr, buildfile_pathname, 1 + line_index, stored_pathname); \
  1394.                      default_scriptcmd_params.attr = current_scriptcmd_params.attr; \
  1395.                   } } while (0)
  1396.                #define APPLY_DEFAULT_ATTR_STR(attr,descr,fmt) do { if (((default_scriptcmd_params.attr == NULL) && (current_scriptcmd_params.attr != NULL)) || ((default_scriptcmd_params.attr != NULL) && (current_scriptcmd_params.attr == NULL)) || ((default_scriptcmd_params.attr != NULL) && (current_scriptcmd_params.attr != NULL) && (strcmp (current_scriptcmd_params.attr, default_scriptcmd_params.attr) != 0))) { \
  1397.                   LOG_INFO ("changing default " descr " from " fmt " to " fmt " by attribute at \"%s\" line %zd of inline document '%s'", (default_scriptcmd_params.attr != NULL ? default_scriptcmd_params.attr : "none"), current_scriptcmd_params.attr, buildfile_pathname, 1 + line_index, stored_pathname); \
  1398.                      if (default_scriptcmd_params.attr != NULL) free (default_scriptcmd_params.attr); \
  1399.                      default_scriptcmd_params.attr = strdup (current_scriptcmd_params.attr); \
  1400.                      ASSERT_WITH_ERRNO (default_scriptcmd_params.attr != NULL); \
  1401.                      default_scriptcmd_params.attr = current_scriptcmd_params.attr; \
  1402.                   } } while (0)
  1403.                APPLY_DEFAULT_ATTR_STR (argv0,                          "executable name",                 "\"%s\"");
  1404.                APPLY_DEFAULT_ATTR_NUM (cpu_number,                     "CPU mask",                        "0%o");
  1405.                APPLY_DEFAULT_ATTR_NUM (is_external,                    "external command flag",           "0%o");
  1406.                APPLY_DEFAULT_ATTR_NUM (priority,                       "scheduling priority",             "0%o");
  1407.                APPLY_DEFAULT_ATTR_NUM (sched_policy,                   "scheduling policy",               "0%o");
  1408.                APPLY_DEFAULT_ATTR_NUM (aps_partindex,                  "APS partition index",             "0%o");
  1409.                APPLY_DEFAULT_ATTR_NUM (is_session_leader,              "session leader flag",             "0%o");
  1410.                APPLY_DEFAULT_ATTR_NUM (is_background_task,             "background task flag",            "0%o");
  1411.                APPLY_DEFAULT_ATTR_NUM (has_debug_flag,                 "debug flag",                      "0%o");
  1412.                #undef APPLY_DEFAULT_ATTR_STR
  1413.                #undef APPLY_DEFAULT_ATTR_NUM
  1414.             }
  1415.  
  1416.             // release the contextual argv/envp arrays
  1417.             STRINGARRAY_FREE (&line_argv);
  1418.             STRINGARRAY_FREE (&line_envp);
  1419.  
  1420.          } // end for (line_index = 0; Buffer_GetNthLine (&entry_parms->data, line_index, &current_line); line_index++)
  1421.          Buffer_Forget (&entry_parms->data); // free the inline specification once it's parsed
  1422.  
  1423.          ASSERT_WITH_ERRNO (Buffer_AppendByteArray (&compiled_script, "\x00\x00\x00\x00")); // terminate the compiled boot script with a 4-byte trailer
  1424.          entry_parms->data.bytes = compiled_script.bytes; // and steal the compiled boot script buffer
  1425.          entry_parms->data.size = compiled_script.size;
  1426.       } // end of "is compiled bootscript"
  1427.  
  1428.       // do we already know the data for this data blob ?
  1429.       if (entry_parms->data.bytes != NULL)
  1430.       {
  1431.          entry_parms->mtime = entry_parms->mtime_for_inline_files; // if so, set it a mtime equal to the mtime to use for inline files
  1432.          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);
  1433.       }
  1434.       else if (buildhost_pathname != NULL) // else was a source file pathname supplied ?
  1435.       {
  1436.          resolved_pathname = resolve_pathname (buildhost_pathname, entry_parms->search); // locate the file
  1437.          if (resolved_pathname == NULL)
  1438.          {
  1439.             if (entry_parms->should_allow_nonexistent_files)
  1440.             {
  1441.                LOG_WARNING ("filesystem entry \"%s\" specified in \"%s\" line %d not found on build host: ignoring", buildhost_pathname, buildfile_pathname, lineno);
  1442.                return; // if we're allowed to continue when a file to add doesn't exist, do so, else die with an error message
  1443.             }
  1444.             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));
  1445.          }
  1446.          if (!Buffer_ReadFromFile (&entry_parms->data, resolved_pathname))
  1447.             DIE_WITH_EXITCODE (1, "filesystem entry \"%s\" specified in \"%s\" line %d can't be read from \"%s\": %s", buildhost_pathname, buildfile_pathname, lineno, resolved_pathname, strerror (errno));
  1448.          stat (resolved_pathname, &stat_buf); // can't fail, since we could read it
  1449.          if (entry_parms->mtime == UINT32_MAX)
  1450.             entry_parms->mtime = (uint32_t) stat_buf.st_mtime;
  1451.          if ((entry_parms->data.size > 52) && (memcmp (entry_parms->data.bytes, ELF_MAGIC_STR, 4) == 0))
  1452.             entry_parms->st_mode |= 0111; // add +x permissions to ELF entries (undocumented mkifs behaviour)
  1453.          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);
  1454.       }
  1455.       else
  1456.          DIE_WITH_EXITCODE (1, "unexpected code path: can't store a file without neither explicit contents nor a host pathname. This is a bug in the program. Please contact the author.");
  1457.  
  1458.       // is the file we're storing an ELF file ?
  1459.       #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
  1460.       if ((entry_parms->data.size > 52) // file is big enough to contain an ELF header
  1461.           && (memcmp (ELF_GET_STRING (ELFHDR, ELFHDR, magic), ELF_MAGIC_STR, 4) == 0)) // file starts with the ELF magic
  1462.       {
  1463.          // is the file we're storing a relocatable executable (i.e. a dynamic library) and should we check for its canonical name ?
  1464.          if ((ELF_GET_NUMERIC (ELFHDR, ELFHDR, type) == ELF_TYPE_DYNAMICLIB) && entry_parms->should_autosymlink_dylib)
  1465.          {
  1466.             // locate the sections we need (the dynamic section and its strings table)
  1467.             const elf_section_header_t *shdr_dynamic = elf_get_section_header_by_name (ELFHDR, ".dynamic");
  1468.             const elf_section_header_t *shdr_dynstr = elf_get_section_header_by_name (ELFHDR, ".dynstr");
  1469.  
  1470.             // make sure we have both the dynamic section header and its own strings table header
  1471.             if ((shdr_dynamic != NULL) && (shdr_dynstr != NULL))
  1472.             {
  1473.                dynamic_strings = (char *) &entry_parms->data.bytes[ELF_GET_NUMERIC (ELFHDR, shdr_dynstr, file_offset)]; // quick access to dynamic sections strings table
  1474.  
  1475.                // walk through the dynamic section, look for the DT_SONAME entry
  1476.                canonical_dylib_name = NULL; // assume none until told otherwise
  1477.                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)];
  1478.                     (ELF_GET_NUMERIC (ELFHDR, dynamic_entry, tag) != ELF_DT_NULL);
  1479.                     dynamic_entry = (elf_dynamic_section_entry_t *) ((uint8_t *) dynamic_entry + ELF_STRUCT_SIZE (ELFHDR, dynamic_entry)))
  1480.                   if (ELF_GET_NUMERIC (ELFHDR, dynamic_entry, tag) == ELF_DT_SONAME)
  1481.                   {
  1482.                      canonical_dylib_name = dynamic_strings + ELF_GET_NUMERIC (ELFHDR, dynamic_entry, value);
  1483.                      break;
  1484.                   }
  1485.  
  1486.                // do we have the canonical dylib name AND does it differ from the name under which we'll be storing this dylib ?
  1487.                filename_bit = strrchr (stored_pathname, '/');
  1488.                if (filename_bit != NULL)
  1489.                {
  1490.                   filename_bit++;
  1491.                   pathbit_len = filename_bit - stored_pathname;
  1492.                }
  1493.                else
  1494.                {
  1495.                   filename_bit = stored_pathname;
  1496.                   pathbit_len = 0;
  1497.                }
  1498.                if ((canonical_dylib_name != NULL) && (canonical_dylib_name[0] != 0) && (strcmp (canonical_dylib_name, filename_bit) != 0))
  1499.                {
  1500.                   original_stored_pathname = stored_pathname; // if so, remember to create a symlink here
  1501.                   if (pathbit_len > 0)
  1502.                   {
  1503.                      strncpy_s (candidate_pathname, MAXPATHLEN, stored_pathname, pathbit_len);
  1504.                      strcpy_s (&candidate_pathname[pathbit_len], MAXPATHLEN - pathbit_len, canonical_dylib_name);
  1505.                   }
  1506.                   else
  1507.                      strcpy_s (candidate_pathname, MAXPATHLEN, canonical_dylib_name);
  1508.                   stored_pathname = candidate_pathname;
  1509.                }
  1510.             }
  1511.          } // end if the file we're storing is a dylib
  1512.  
  1513.          // now strip this ELF file if necessary
  1514.          if (!(entry_parms->extra_ino_flags & IFS_INO_PROCESSED_ELF))
  1515.          {
  1516.             Buffer_StripELFFile (&entry_parms->data, (const char **) saved_ELF_sections, saved_ELF_section_count, false, stored_pathname); // strip the ELF file à la mkifs
  1517.             entry_parms->extra_ino_flags |= IFS_INO_PROCESSED_ELF; // mark this inode as a preprocessed ELF file
  1518.          } // end if the file is not yet a processed ELF
  1519.       } // end if the file we're storing is an ELF file
  1520.       #undef ELFHDR // undefine the macro that used to always point to the ELF header at the beginning of the file
  1521.    }
  1522.  
  1523.    // have a pointer to where the stored pathname actually starts, without the leading slash
  1524.    stored_pathname_without_leading_slash = stored_pathname[0] == '/' ? &stored_pathname[1] : stored_pathname;
  1525.  
  1526.    // see if this item already has an entry in the current list of filesystem entries
  1527.    for (fsentry_index = 0; fsentry_index < *fsentry_count; fsentry_index++)
  1528.    {
  1529.       fsentry = &(*fsentries)[fsentry_index]; // quick access to fs entry slot
  1530.       if (   (S_ISDIR  (fsentry->header.mode) && (strcmp (fsentry->u.dir.path,     stored_pathname_without_leading_slash) == 0))
  1531.           || (S_ISREG  (fsentry->header.mode) && (strcmp (fsentry->u.file.path,    stored_pathname_without_leading_slash) == 0))
  1532.           || (S_ISLNK  (fsentry->header.mode) && (strcmp (fsentry->u.symlink.path, stored_pathname_without_leading_slash) == 0))
  1533.           || (S_ISFIFO (fsentry->header.mode) && (strcmp (fsentry->u.symlink.path, stored_pathname_without_leading_slash) == 0)))
  1534.          break; // stop searching as soon as we find a duplicate
  1535.    }
  1536.  
  1537.    // is there already an entry for this item ?
  1538.    if (fsentry_index < *fsentry_count)
  1539.    {
  1540.       // if we should NOT ignore duplicates, bomb out (except for the root entry which is implicitly defined), else just return
  1541.       if (strcmp (stored_pathname, "/") == 0)
  1542.          return; // the root entry is implicitly defined: do not warn about it
  1543.       else if (entry_parms->should_ignore_duplicates)
  1544.          LOG_WARNING ("duplicate detected: entry \"%s\" specified in \"%s\" line %d already exists in build file (already defined line %d)", stored_pathname, buildfile_pathname, lineno, (*fsentries)[fsentry_index].UNSAVED_lineno);
  1545.       else
  1546.          DIE_WITH_EXITCODE (1, "duplicate detected: entry \"%s\" specified in \"%s\" line %d already exists in build file (already defined line %d)", stored_pathname, buildfile_pathname, lineno, (*fsentries)[fsentry_index].UNSAVED_lineno);
  1547.    }
  1548.    else // this is a new entry: grow filesystem entries array to hold one more slot
  1549.    {
  1550.       reallocated_ptr = realloc (*fsentries, (*fsentry_count + 1) * sizeof (fsentry_t)); // attempt to reallocate
  1551.       ASSERT_WITH_ERRNO (reallocated_ptr); // verify
  1552.       *fsentries = reallocated_ptr; // save reallocated pointer
  1553.       fsentry = &(*fsentries)[*fsentry_count]; // quick access to fs entry slot
  1554.       (*fsentry_count)++; // remember there's one entry more in the array
  1555.    }
  1556.  
  1557.    // save (or update) this entry's parameters
  1558.    fsentry->header.extattr_offset = 0;
  1559.    fsentry->header.ino = entry_parms->extra_ino_flags | (++inode_count);
  1560.    fsentry->header.mode = entry_parms->st_mode;
  1561.    fsentry->header.gid = entry_parms->gid;
  1562.    fsentry->header.uid = entry_parms->uid;
  1563.    fsentry->header.mtime = (entry_parms->mtime == UINT32_MAX ? (uint32_t) time (NULL) : entry_parms->mtime);
  1564.    if (S_ISDIR (entry_parms->st_mode))
  1565.    {
  1566.       fsentry->u.dir.path = strdup (stored_pathname_without_leading_slash);
  1567.  
  1568.       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
  1569.       fsentry->UNSAVED_was_data_written = true; // no data to save
  1570.    }
  1571.    else if (S_ISREG (entry_parms->st_mode))
  1572.    {
  1573.       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
  1574.       fsentry->u.file.size = (uint32_t) entry_parms->data.size;
  1575.       fsentry->u.file.path = strdup (stored_pathname_without_leading_slash);
  1576.  
  1577.       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
  1578.       fsentry->UNSAVED_databuf = malloc (entry_parms->data.size);
  1579.       ASSERT_WITH_ERRNO (fsentry->UNSAVED_databuf);
  1580.       memcpy (fsentry->UNSAVED_databuf, entry_parms->data.bytes, entry_parms->data.size);
  1581.       fsentry->UNSAVED_was_data_written = false; // there *IS* data to save
  1582.    }
  1583.    else if (S_ISLNK (entry_parms->st_mode))
  1584.    {
  1585.       fsentry->u.symlink.sym_offset = (uint16_t) (strlen (stored_pathname_without_leading_slash) + 1);
  1586.       fsentry->u.symlink.sym_size = (uint16_t) entry_parms->data.size;
  1587.       fsentry->u.symlink.path = strdup (stored_pathname_without_leading_slash);
  1588.       fsentry->u.symlink.contents = strdup (entry_parms->data.bytes);
  1589.       ASSERT_WITH_ERRNO (fsentry->u.symlink.contents);
  1590.  
  1591.       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
  1592.       fsentry->UNSAVED_was_data_written = true; // no data to save
  1593.    }
  1594.    else // necessarily a device node
  1595.    {
  1596.       fsentry->u.device.dev  = strtol (entry_parms->data.bytes, NULL, 0); // use strtol() to parse decimal (...), hexadecimal (0x...) and octal (0...) numbers
  1597.       fsentry->u.device.rdev = strtol (strchr (entry_parms->data.bytes, ':') + 1, NULL, 0); // use strtol() to parse decimal (...), hexadecimal (0x...) and octal (0...) numbers
  1598.       fsentry->u.device.path = strdup (stored_pathname_without_leading_slash);
  1599.  
  1600.       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
  1601.       fsentry->UNSAVED_was_data_written = true; // no data to save
  1602.    }
  1603.  
  1604.    fsentry->UNSAVED_lineno = lineno; // save the line number at which this entry was defined, for error reporting
  1605.  
  1606.    // should we also add a symlink to this entry ? (in case we stored a dylib file under its canonical name)
  1607.    if (original_stored_pathname != NULL)
  1608.    {
  1609.       old_data = entry_parms->data.bytes; // backup previous data pointer
  1610.       entry_parms->is_compiled_bootscript = false;
  1611.       entry_parms->should_autosymlink_dylib = false;
  1612.       entry_parms->should_follow_symlinks = false;
  1613.       entry_parms->st_mode = S_IFLNK | 0777; // NOTE: mkifs stores symlink permissions as rwxrwxrwx !
  1614.       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
  1615.       entry_parms->data.bytes = (uint8_t *) ((last_dirsep = strrchr (stored_pathname, '/')) == NULL ? stored_pathname : last_dirsep + 1); // store symlink target in dirent data
  1616.       entry_parms->data.size = strlen (entry_parms->data.bytes);
  1617.       add_fsentry (fsentries, fsentry_count, entry_parms, original_stored_pathname, NULL);
  1618.       entry_parms->data.bytes = old_data; // restore previous data pointer so that it can be freed normally
  1619.    }
  1620.  
  1621. #if 0 // DISABLED: not the right place to do that (causes duplicates)
  1622.    // WEIRD HACK: if we stored a dylib under the form "xxxxx.so.yyyy", then add a symlink as "xxxxx.so". This is to replicate a weird (undocumented?) behaviour of mkifs.
  1623.    if ((canonical_dylib_name != NULL) && ((token = strstr (stored_pathname, ".so.")) != NULL))
  1624.    {
  1625.       old_data = entry_parms->data.bytes; // backup previous data pointer
  1626.       entry_parms->is_compiled_bootscript = false;
  1627.       entry_parms->should_autosymlink_dylib = false;
  1628.       entry_parms->should_follow_symlinks = false;
  1629.       entry_parms->st_mode = S_IFLNK | 0777; // NOTE: mkifs stores symlink permissions as rwxrwxrwx !
  1630.       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
  1631.       entry_parms->data.bytes = (uint8_t *) ((last_dirsep = strrchr (stored_pathname, '/')) == NULL ? stored_pathname : last_dirsep + 1); // store symlink target in dirent data
  1632.       entry_parms->data.size = strlen (entry_parms->data.bytes);
  1633.       token[3] = 0;
  1634.       add_fsentry (fsentries, fsentry_count, entry_parms, stored_pathname, NULL);
  1635.       token[3] = '.';
  1636.       entry_parms->data.bytes = old_data; // restore previous data pointer so that it can be freed normally
  1637.    }
  1638. #endif
  1639.  
  1640.    return; // finished, return to our caller
  1641. }
  1642.  
  1643.  
  1644. static void add_directory_contents_recursively (fsentry_t **fsentries, size_t *fsentry_count, const char *dir_pathname, const size_t start_pathname_len, parms_t *default_parms)
  1645. {
  1646.    // adds the contents of the directory pointed to by dir_pathname to the fsentries array, recursively
  1647.    // start_pathname_len is initialized to the length of dir_pathname by the top caller, and passed down unchanged,
  1648.    // so that each sublevel of the recursion knows the depth of the relative path in which it is.
  1649.  
  1650.    thread_local static char item_pathname[MAXPATHLEN] = "";
  1651.    thread_local static parms_t entry_parms = { 0 };
  1652.    thread_local static struct stat stat_buf = { 0 };
  1653.    thread_local static char major_minor[64];
  1654.  
  1655.    DIR *dirp;
  1656.    struct dirent *dp;
  1657.  
  1658.    // open the directory
  1659.    dirp = opendir (dir_pathname);
  1660.    if (dirp == NULL)
  1661.       DIE_WITH_EXITCODE (1, "unable to open directory \"%s\" for recursive inclusion", dir_pathname);
  1662.  
  1663.    // enumerate its contents
  1664.    while ((dp = readdir (dirp)) != NULL)
  1665.    {
  1666.       if ((strcmp (dp->d_name, ".") == 0) || (strcmp (dp->d_name, "..") == 0))
  1667.          continue; // skip self and parent
  1668.  
  1669.       memcpy (&entry_parms, default_parms, sizeof (parms_t));
  1670.       sprintf_s (item_pathname, sizeof (item_pathname), "%s/%s", dir_pathname, dp->d_name); // construct item's pathname
  1671.       ASSERT_WITH_ERRNO (stat (item_pathname, &stat_buf) == 0); // peek info about this entry (or die trying)
  1672.       if (S_ISDIR (stat_buf.st_mode))
  1673.       {
  1674.          entry_parms.st_mode |= entry_parms.dperms; // apply DIRECTORY default permissions
  1675.          add_fsentry (fsentries, fsentry_count, &entry_parms, &item_pathname[start_pathname_len], NULL); // add a filesystem entry of type "directory"
  1676.          add_directory_contents_recursively (fsentries, fsentry_count, item_pathname, start_pathname_len, default_parms); // dwell into this directory and add its children recursively
  1677.       }
  1678.       else if (S_ISLNK (stat_buf.st_mode))
  1679.       {
  1680.          entry_parms.st_mode |= 0777; // NOTE: mkifs sets symlink permissions to rwxrwxrwx !?
  1681.          add_fsentry (fsentries, fsentry_count, &entry_parms, &item_pathname[start_pathname_len], item_pathname); // add a filesystem entry of type "link"
  1682.       }
  1683.       else if (S_ISREG (stat_buf.st_mode))
  1684.       {
  1685.          entry_parms.st_mode |= entry_parms.perms; // apply FILE default permissions
  1686.          add_fsentry (fsentries, fsentry_count, &entry_parms, &item_pathname[start_pathname_len], item_pathname); // add a filesystem entry of type "regular file"
  1687.       }
  1688.       else if (S_ISFIFO (stat_buf.st_mode))
  1689.       {
  1690.          entry_parms.st_mode |= entry_parms.perms; // apply FILE default permissions
  1691.          sprintf_s (major_minor, sizeof (major_minor), "%u:%u", (unsigned int) major (stat_buf.st_rdev), (unsigned int) minor (stat_buf.st_rdev));
  1692.          entry_parms.data.bytes = major_minor;
  1693.          add_fsentry (fsentries, fsentry_count, &entry_parms, &item_pathname[start_pathname_len], NULL); // add a filesystem entry of type "FIFO"
  1694.       }
  1695.       else
  1696.          LOG_WARNING ("ignoring unsupported directory entry: \"%s\" (type 0%o)", item_pathname, stat_buf.st_mode & S_IFMT);
  1697.    }
  1698.  
  1699.    closedir (dirp); // finished parsing this level, close the directory handle
  1700.    return; // and return to our caller
  1701. }
  1702.  
  1703.  
  1704. static int fsentry_compare_pathnames_cb (const void *a, const void *b)
  1705. {
  1706.    // qsort() callback that compares two imagefs filesystem entries and sort them alphabetically by pathname
  1707.  
  1708.    const fsentry_t *entry_a = (const fsentry_t *) a;
  1709.    const fsentry_t *entry_b = (const fsentry_t *) b;
  1710.    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)));
  1711.    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)));
  1712.    return (strcmp (pathname_a, pathname_b));
  1713. }
  1714.  
  1715.  
  1716. static void parse_line (FILE *buildfile_fp, char *line_buffer, fsentry_t **fsentries, size_t *fsentry_count, parms_t *default_parms)
  1717. {
  1718.    thread_local static char specified_pathname[MAXPATHLEN] = ""; // exactly as specified in the build file
  1719.    thread_local static char path_on_buildhost[MAXPATHLEN] = "";
  1720.    thread_local static char path_in_ifs[MAXPATHLEN] = "";
  1721.    thread_local 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)
  1722.  
  1723.    bool should_discard_inline_contents;
  1724.    bool is_quoted_context;
  1725.    bool is_escaped_char;
  1726.    struct stat stat_buf;
  1727.    struct tm utc_time;
  1728.    void *reallocated_ptr;
  1729.    size_t allocated_size;
  1730.    size_t string_len;
  1731.    char *attrblock_start;
  1732.    char *pathname_start;
  1733.    char *write_ptr;
  1734.    char *line_ptr;
  1735.    char *value;
  1736.    char *token;
  1737.    char *sep;
  1738.    char *ctx;
  1739.    int read_char;
  1740.  
  1741.    line_ptr = line_buffer;
  1742.    while ((*line_ptr != 0) && isspace (*line_ptr))
  1743.       line_ptr++; // skip leading spaces
  1744.  
  1745.    if ((*line_ptr == 0) || (*line_ptr == '#'))
  1746.       return; // don't process empty lines and comments
  1747.  
  1748.    string_len = (int) strlen (line_buffer);
  1749.    while ((string_len > 0) && ((line_buffer[string_len - 1] == '\r') || (line_buffer[string_len - 1] == '\n')))
  1750.       line_buffer[--string_len] = 0; // chop off carriage returns and newlines for easier debug output
  1751.  
  1752.    // reset entry values
  1753.    memcpy (&entry_parms, default_parms, sizeof (parms_t));
  1754.    path_in_ifs[0] = 0;
  1755.    path_on_buildhost[0] = 0;
  1756.    specified_pathname[0] = 0;
  1757.    should_discard_inline_contents = false;
  1758.  
  1759.    // does this line start with an attribute block ?
  1760.    if (*line_ptr == '[')
  1761.    {
  1762.       line_ptr++; // skip the leading square bracket
  1763.       attrblock_start = line_ptr; // remember where it starts
  1764.       is_quoted_context = false;
  1765.       while ((*line_ptr != 0) && !((*line_ptr == ']') && (line_ptr[-1] != '\\') && !is_quoted_context))
  1766.       {
  1767.          if (*line_ptr == '"')
  1768.             is_quoted_context ^= true; // remember when we're between quotes
  1769.          else if (!is_quoted_context && (*line_ptr == ' '))
  1770.             *line_ptr = RECORD_SEP[0]; // turn all spaces outside quoted contexts into an ASCII record separator to ease token splitting
  1771.          line_ptr++; // reach the next unescaped closing square bracket
  1772.       }
  1773.       if (*line_ptr != ']')
  1774.       {
  1775.          LOG ("warning", 0, "syntax error in \"%s\" line %d: unterminated attributes block (skipping)", buildfile_pathname, lineno);
  1776.          return; // invalid attribute block, skip line
  1777.       }
  1778.       *line_ptr = 0; // end the attribute block so that it is a parsable C string
  1779.  
  1780.       // now parse the attribute tokens
  1781.       // DOCUMENTATION: https://www.qnx.com/developers/docs/8.0/com.qnx.doc.neutrino.utilities/topic/m/mkifs.html#mkifs__description
  1782.       token = strtok_r (attrblock_start, RECORD_SEP, &ctx);
  1783.       while (token != NULL)
  1784.       {
  1785.          // evaluate attribute token
  1786.          #define REACH_TOKEN_VALUE() do { value = strchr (token, '=') + 1; if (*value == '"') value++; } while (0)
  1787.          if (false) {}
  1788.          else if (strncmp (token, "prefix=",  7) == 0) { REACH_TOKEN_VALUE (); entry_parms.prefix =  (*value == '/' ? value + 1 : value); } // skip possible leading slash in prefix (NOTE: stolen pointer. Do not free.)
  1789.          else if (strncmp (token, "uid=",     4) == 0) { REACH_TOKEN_VALUE (); entry_parms.uid     = (int) read_integer (value, 10); }
  1790.          else if (strncmp (token, "gid=",     4) == 0) { REACH_TOKEN_VALUE (); entry_parms.gid     = (int) read_integer (value, 10); }
  1791.          else if (strncmp (token, "dperms=",  7) == 0) { REACH_TOKEN_VALUE (); entry_parms.dperms  = (int) read_integer (value, 8); }
  1792.          else if (strncmp (token, "perms=",   6) == 0) { REACH_TOKEN_VALUE (); entry_parms.perms   = (int) read_integer (value, 8); }
  1793.          else if (strncmp (token, "type=",    5) == 0) { REACH_TOKEN_VALUE ();
  1794.             if      (strcmp (value, "dir")  == 0) entry_parms.st_mode = S_IFDIR;
  1795.             else if (strcmp (value, "file") == 0) entry_parms.st_mode = S_IFREG;
  1796.             else if (strcmp (value, "link") == 0) entry_parms.st_mode = S_IFLNK;
  1797.             else if (strcmp (value, "fifo") == 0) entry_parms.st_mode = S_IFIFO;
  1798.             else DIE_WITH_EXITCODE (1, "invalid 'type' attribute in \"%s\" line %d: '%s'", buildfile_pathname, lineno, value);
  1799.          }
  1800.          else if (strncmp (token, "image=",   6) == 0) { REACH_TOKEN_VALUE ();
  1801.             image_base = (uint32_t) read_integer (value, 0); // read image base address
  1802.             if ((sep = strchr (value, '-')) != NULL) image_end       = (uint32_t) read_integer (sep + 1, 0); // if we have a dash, read optional image end (TODO: check this value and produce an error in the relevant case. Not important.)
  1803.             if ((sep = strchr (value, ',')) != NULL) image_maxsize   = (uint32_t) read_integer (sep + 1, 0); // if we have a comma, read optional image max size
  1804.             if ((sep = strchr (value, '=')) != NULL) image_totalsize = (uint32_t) read_integer (sep + 1, 0); // if we have an equal sign, read optional image padding size
  1805.             if ((sep = strchr (value, '%')) != NULL) image_align     = (uint32_t) read_integer (sep + 1, 0); // if we have a modulo sign, read optional image aligmnent
  1806.             LOG_INFO ("image 0x%x-0x%x maxsize %d totalsize %d align %d", image_base, image_end, image_maxsize, image_totalsize, image_align);
  1807.          }
  1808.          else if (strncmp (token, "virtual=", 8) == 0) { REACH_TOKEN_VALUE ();
  1809.             if ((sep = strchr (value, ',')) != NULL) // do we have a comma separating (optional) processor and boot file name ?
  1810.             {
  1811.                *sep = 0;
  1812.                if (strcmp (value, "x86_64") == 0)
  1813.                {
  1814.                   image_processor = "x86_64"; // save processor
  1815.                   image_processor_base = "x86_64"; // save processor base
  1816.                   image_pagesize = 4 * 1024; // Intel processors use 4 Kb pages
  1817.                }
  1818.                else if (strcmp (value, "aarch64le") == 0)
  1819.                {
  1820.                   image_processor = "aarch64le"; // save processor
  1821.                   image_processor_base = "aarch64"; // save processor base
  1822.                   image_pagesize = 16 * 1024; // ARM processors use 16 Kb pages
  1823.                }
  1824.                else
  1825.                   DIE_WITH_EXITCODE (1, "unrecognized processor type in 'virtual' attribute in \"%s\" line %d: '%s'", buildfile_pathname, lineno, value);
  1826.                value = sep + 1;
  1827.             }
  1828.             boot_type = (strstr (value, "uefi") != NULL ? BOOTTYPE_UEFI : BOOTTYPE_BIOS); // FIXME: this should be a boot FILE, not a hardcoded tag
  1829.             if (boot_type == BOOTTYPE_UEFI)
  1830.             {
  1831.                if (startupfile_pathname == NULL) // FIXME: HACK until I figure out how to re-create it
  1832.                   DIE_WITH_EXITCODE (1, "creating bootable UEFI images requires the --startupfile command-line option in \"%s\" line %d", buildfile_pathname, lineno);
  1833.                bootfile_size = ROUND_TO_UPPER_MULTIPLE (sizeof (uefi64_header_t), 512); // round to upper filesystem block (PE header constraint)
  1834.                LOG_INFO ("processor \"%s\" bootfile <generated, UEFI>\n", image_processor);
  1835.             }
  1836.             else
  1837.             {
  1838.                if ((bootfile_pathname == NULL) || (startupfile_pathname == NULL)) // FIXME: HACK until I figure out how to re-create them
  1839.                   DIE_WITH_EXITCODE (1, "creating bootable BIOS images requires the --bootfile and --startupfile command-line options in \"%s\" line %d", buildfile_pathname, lineno);
  1840.                if (stat (bootfile_pathname, &stat_buf) != 0)
  1841.                   DIE_WITH_EXITCODE (1, "unable to stat the boot file \"%s\" specified in \"%s\" line %d: %s", bootfile_pathname, buildfile_pathname, lineno, strerror (errno));
  1842.                bootfile_size = stat_buf.st_size; // save preboot file size
  1843.                LOG_INFO ("processor \"%s\" bootfile \"%s\"\n", image_processor, bootfile_pathname);
  1844.             }
  1845.             entry_parms.is_bootstrap_file = true;
  1846.          }
  1847.          else if (strncmp (token, "mtime=", 6) == 0) { REACH_TOKEN_VALUE (); if (strcmp (value, "*") == 0) entry_parms.mtime = UINT32_MAX; else {
  1848.                // value *must* be "YYYY-MM-DD-HH:MM:SS" by specification
  1849.                memset (&utc_time, 0, sizeof (utc_time));
  1850.                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)
  1851.                {
  1852.                   LOG_WARNING ("syntax error in \"%s\" line %d: mtime specification not in YYYY-MM-DD-HH:MM:SS format (skipping)", buildfile_pathname, lineno);
  1853.                   continue; // invalid attribute block, skip line
  1854.                }
  1855.                utc_time.tm_mon--; // convert month from [1-12] to [0-11]
  1856.                entry_parms.mtime = (uint32_t) mktime (&utc_time);
  1857.             }
  1858.          }
  1859.          else if (strncmp (token, "compress=", 9) == 0) { REACH_TOKEN_VALUE (); startup_header_compression_flag = (strcmp (value, "1") == 0 ? STARTUP_HDR_FLAGS1_COMPRESS_ZLIB : (strcmp (value, "2") == 0 ? STARTUP_HDR_FLAGS1_COMPRESS_LZO : STARTUP_HDR_FLAGS1_COMPRESS_UCL)); }
  1860.          else if (strcmp (token, "+compress")   == 0) startup_header_compression_flag = STARTUP_HDR_FLAGS1_COMPRESS_UCL;
  1861.          else if (strcmp (token, "-compress")   == 0) startup_header_compression_flag = STARTUP_HDR_FLAGS1_COMPRESS_NONE;
  1862.          else if (strcmp (token, "+script")     == 0) entry_parms.is_compiled_bootscript         = true;
  1863.          else if (strcmp (token, "-script")     == 0) entry_parms.is_compiled_bootscript         = false;
  1864.          else if (strcmp (token, "+followlink") == 0) entry_parms.should_follow_symlinks         = true;
  1865.          else if (strcmp (token, "-followlink") == 0) entry_parms.should_follow_symlinks         = false;
  1866.          else if (strcmp (token, "+autolink")   == 0) entry_parms.should_autosymlink_dylib       = true;
  1867.          else if (strcmp (token, "-autolink")   == 0) entry_parms.should_autosymlink_dylib       = false;
  1868.          else if (strcmp (token, "+keeplinked") == 0) entry_parms.should_keep_ld_output          = true;
  1869.          else if (strcmp (token, "-keeplinked") == 0) entry_parms.should_keep_ld_output          = false;
  1870.          else if (strcmp (token, "+dupignore")  == 0) entry_parms.should_ignore_duplicates       = true;
  1871.          else if (strcmp (token, "-dupignore")  == 0) entry_parms.should_ignore_duplicates       = false;
  1872.          else if (strcmp (token, "+optional")   == 0) entry_parms.should_allow_nonexistent_files = true;
  1873.          else if (strcmp (token, "-optional")   == 0) entry_parms.should_allow_nonexistent_files = false;
  1874.          else LOG_WARNING ("unimplemented attribute in \"%s\" line %d: '%s'", buildfile_pathname, lineno, token);
  1875.          #undef REACH_TOKEN_VALUE
  1876.  
  1877.          token = strtok_r (NULL, RECORD_SEP, &ctx); // proceed to next attribute token
  1878.       }
  1879.  
  1880.       line_ptr++; // reach the next character
  1881.       while ((*line_ptr != 0) && isspace (*line_ptr))
  1882.          line_ptr++; // skip leading spaces
  1883.  
  1884.       // are we at the end of the line ? if so, it means the attribute values that are set should become the default
  1885.       if ((*line_ptr == 0) || (*line_ptr == '#'))
  1886.       {
  1887.          #define APPLY_DEFAULT_ATTR_NUM(attr,descr,fmt) do { if (entry_parms.attr != default_parms->attr) { \
  1888.                LOG_INFO ("changing default " descr " from " fmt " to " fmt " by attribute at \"%s\" line %d", default_parms->attr, entry_parms.attr, buildfile_pathname, lineno); \
  1889.                default_parms->attr = entry_parms.attr; \
  1890.             } } while (0)
  1891.          #define APPLY_DEFAULT_ATTR_STR(attr,descr,fmt) do { if (((default_parms->attr == NULL) && (entry_parms.attr != NULL)) || ((default_parms->attr != NULL) && (entry_parms.attr == NULL)) || ((default_parms->attr != NULL) && (entry_parms.attr != NULL) && (strcmp (entry_parms.attr, default_parms->attr) != 0))) { \
  1892.             LOG_INFO ("changing default " descr " from " fmt " to " fmt " by attribute at \"%s\" line %d", (default_parms->attr != NULL ? default_parms->attr : "none"), entry_parms.attr, buildfile_pathname, lineno); \
  1893.                if (default_parms->attr != NULL) free (default_parms->attr); \
  1894.                default_parms->attr = strdup (entry_parms.attr); \
  1895.                ASSERT_WITH_ERRNO (default_parms->attr != NULL); \
  1896.             } } while (0)
  1897.          //APPLY_DEFAULT_ATTR_STR (new_cwd,                        "current working directory",       "\"%s\"");
  1898.          APPLY_DEFAULT_ATTR_STR (search,                         "search path list",                "\"%s\"");
  1899.          APPLY_DEFAULT_ATTR_STR (prefix,                         "prefix",                          "\"%s\"");
  1900.          APPLY_DEFAULT_ATTR_NUM (dperms,                         "directory permissions",           "0%o");
  1901.          APPLY_DEFAULT_ATTR_NUM (perms,                          "file permissions",                "0%o");
  1902.          APPLY_DEFAULT_ATTR_NUM (uid,                            "owner ID",                        "%d");
  1903.          APPLY_DEFAULT_ATTR_NUM (gid,                            "group ID",                        "%d");
  1904.          APPLY_DEFAULT_ATTR_NUM (st_mode,                        "inode type",                      "0%o");
  1905.          APPLY_DEFAULT_ATTR_NUM (is_compiled_bootscript,         "compiled script state",           "%d");
  1906.          APPLY_DEFAULT_ATTR_NUM (should_follow_symlinks,         "symlink resolution flag",         "%d");
  1907.          APPLY_DEFAULT_ATTR_NUM (should_autosymlink_dylib,       "dylib canonical name symlinking", "%d");
  1908.          APPLY_DEFAULT_ATTR_NUM (should_keep_ld_output,          "linker output preservation flag", "%d");
  1909.          APPLY_DEFAULT_ATTR_NUM (should_ignore_duplicates,       "ignore duplicates flag",          "%d");
  1910.          APPLY_DEFAULT_ATTR_NUM (should_allow_nonexistent_files, "ignore nonexistent files flag",   "%d");
  1911.          #undef APPLY_DEFAULT_ATTR_STR
  1912.          #undef APPLY_DEFAULT_ATTR_NUM
  1913.          return; // end of line reached, proceed to the next line
  1914.       }
  1915.       // end of attributes parsing
  1916.    } // end of "this line starts with an attributes block"
  1917.  
  1918.    // there's data in this line. We expect a filename in the IFS. Read it and unescape escaped characters
  1919.    is_quoted_context = (*line_ptr == '"');
  1920.    if (is_quoted_context)
  1921.       line_ptr++; // skip a possible initial quote
  1922.    write_ptr = specified_pathname;
  1923.    while ((*line_ptr != 0) && ((!is_quoted_context && (*line_ptr != '=') && !isspace (*line_ptr)) || (is_quoted_context && (*line_ptr != '"'))))
  1924.    {
  1925.       if (*line_ptr == '\\')
  1926.       {
  1927.          line_ptr++;
  1928.          *write_ptr++ = *line_ptr; // unescape characters that are escaped with '\'
  1929.       }
  1930.       else
  1931.          *write_ptr++ = *line_ptr;
  1932.       line_ptr++;
  1933.    }
  1934.    *write_ptr = 0; // terminate the string
  1935.    if (is_quoted_context && (*line_ptr == '"'))
  1936.       line_ptr++; // skip a possible final quote
  1937.  
  1938.    // we reached a space OR an equal sign
  1939.    while ((*line_ptr != 0) && isspace (*line_ptr))
  1940.       line_ptr++; // skip optional spaces after the filename in the IFS
  1941.  
  1942.    // do we have an equal sign ?
  1943.    if (*line_ptr == '=')
  1944.    {
  1945.       line_ptr++; // skip the equal sign
  1946.       while ((*line_ptr != 0) && isspace (*line_ptr))
  1947.          line_ptr++; // skip optional spaces after the equal sign
  1948.  
  1949.       if (*line_ptr == 0)
  1950.       {
  1951.          LOG_WARNING ("syntax error in \"%s\" line %d: missing data specification after equal sign (skipping)", buildfile_pathname, lineno);
  1952.          return; // invalid symlink specification, skip line
  1953.       }
  1954.  
  1955.       // it may be either a path or a contents definition. Is it a content definition ?
  1956.       if (*line_ptr == '{')
  1957.       {
  1958.          allocated_size = 0;
  1959.  
  1960.          line_ptr++; // skip the leading content definition
  1961.          is_escaped_char = false;
  1962.          for (;;)
  1963.          {
  1964.             read_char = fgetc (buildfile_fp);
  1965.             if (read_char == EOF)
  1966.                DIE_WITH_EXITCODE (1, "syntax error in \"%s\" line %d: unterminated contents block (end of file reached)", buildfile_pathname, lineno); // invalid contents block
  1967.             else if (read_char == '\r')
  1968.                continue; // just ignore carriage returns (Microsoft end of line format)
  1969.             else if ((read_char == '\\') && !is_escaped_char)
  1970.                is_escaped_char = true; // remember the next char is escaped
  1971.             else if ((read_char == '}') && !is_escaped_char)
  1972.                break; // found an unescaped closing bracked, stop parsing
  1973.             else
  1974.             {
  1975.                is_escaped_char = false; // any other char, meaning the next one will not be escaped
  1976.                if (!should_discard_inline_contents) // only store the contents if we do NOT know the data yet
  1977.                {
  1978.                   if (entry_parms.data.size == allocated_size) // reallocate in 16k blocks
  1979.                   {
  1980.                      reallocated_ptr = realloc (entry_parms.data.bytes, allocated_size + 16384);
  1981.                      ASSERT_WITH_ERRNO (reallocated_ptr);
  1982.                      entry_parms.data.bytes = reallocated_ptr;
  1983.                      allocated_size += 16384;
  1984.                   }
  1985.                   entry_parms.data.bytes[entry_parms.data.size++] = read_char;
  1986.                }
  1987.                if (read_char == '\n')
  1988.                   lineno++; // update line counter as we parse the inline content
  1989.             }
  1990.          } // end for
  1991.       }
  1992.       else // not a content definition between { brackets }, must be either a pathname on the build host, or the target of a symlink
  1993.       {
  1994.          is_quoted_context = (*line_ptr == '"');
  1995.          if (is_quoted_context)
  1996.             line_ptr++; // skip a possible initial quote
  1997.          pathname_start = line_ptr; // remember where the specified pathname starts
  1998.          write_ptr = line_ptr; // now unescape all characters
  1999.          while ((*line_ptr != 0) && ((!is_quoted_context && !isspace (*line_ptr)) || (is_quoted_context && (*line_ptr != '"'))))
  2000.          {
  2001.             if (*line_ptr == '\\')
  2002.             {
  2003.                line_ptr++;
  2004.                *write_ptr++ = *line_ptr; // unescape characters that are escaped with '\'
  2005.             }
  2006.             else
  2007.                *write_ptr++ = *line_ptr;
  2008.             line_ptr++;
  2009.          }
  2010.          *write_ptr = 0; // terminate the string
  2011.          if (is_quoted_context && (*line_ptr == '"'))
  2012.             line_ptr++; // skip a possible final quote
  2013.  
  2014.          if (S_ISLNK (entry_parms.st_mode)) // are we storing a symlink ?
  2015.             ASSERT_WITH_ERRNO (Buffer_InitWithCString (&entry_parms.data, pathname_start)); // if so, store the symlink target as the dirent's blob data
  2016.          else // it's a build host filesystem path
  2017.             strcpy_s (path_on_buildhost, sizeof (path_on_buildhost), pathname_start); // the path on the build host is given after the equal sign
  2018.       }
  2019.    }
  2020.    else // no equal sign, meaning the file will have the same name on the build host filesystem
  2021.    {
  2022.       // consistency check: symlinks MUST have an equal sign
  2023.       if (entry_parms.st_mode == S_IFLNK)
  2024.       {
  2025.          LOG_WARNING ("syntax error in \"%s\" line %d: missing equal sign and symlink target (skipping)", buildfile_pathname, lineno);
  2026.          return; // invalid symlink specification, skip line
  2027.       }
  2028.  
  2029.       // UNLESS we know we are storing (=creating empty) a directory, the path on the build host is the one specified
  2030.       if (S_ISDIR (entry_parms.st_mode))
  2031.          path_on_buildhost[0] = 0; // we're storing a new empty directory: path on build host is nil
  2032.       else
  2033.          strcpy_s (path_on_buildhost, sizeof (path_on_buildhost), specified_pathname); // the path on the build host is the one specified
  2034.  
  2035.       if ((specified_pathname[0] != '/') && ((token = strrchr (specified_pathname, '/')) != NULL))
  2036.          memmove (specified_pathname, token + 1, strlen (token + 1) + 1); // unless it's absolute, the path in the IFS is the BASENAME of the path specified
  2037.    }
  2038.  
  2039.    // was the specified path in the build file absolute or relative ?
  2040.    if (specified_pathname[0] == '/')
  2041.       strcpy_s (path_in_ifs, sizeof (path_in_ifs), specified_pathname); // path is absolute, use it verbatim
  2042.    else // path is relative
  2043.    {
  2044.       string_len = sprintf_s (path_in_ifs, sizeof (path_in_ifs), "%s", (entry_parms.prefix != NULL ? entry_parms.prefix : "")); // use path prefix
  2045.       while ((string_len > 0) && (path_in_ifs[string_len - 1] == '/'))
  2046.          string_len--; // chop off any trailing slashes from prefix
  2047.       path_in_ifs[string_len++] = '/'; // add ONE trailing slash
  2048.       strcpy_s (&path_in_ifs[string_len], sizeof (path_in_ifs) - string_len, specified_pathname); // construct an absolute path with the IFS prefix
  2049.    }
  2050.  
  2051.    // now add this entry to the image filesystem
  2052.    if (S_ISDIR (entry_parms.st_mode))
  2053.       entry_parms.st_mode |= entry_parms.dperms; // directory
  2054.    else if (S_ISLNK (entry_parms.st_mode))
  2055.       entry_parms.st_mode |= 0777; // symlink (NOTE: mkifs sets symlink permissions to rwxrwxrwx !?)
  2056.    else if (S_ISREG (entry_parms.st_mode))
  2057.       entry_parms.st_mode |= entry_parms.perms; // file
  2058.    else
  2059.       entry_parms.st_mode |= entry_parms.perms; // device node
  2060.  
  2061.    add_fsentry (fsentries, fsentry_count, &entry_parms, path_in_ifs, path_on_buildhost); // and add filesystem entry
  2062.  
  2063.    if (entry_parms.data.bytes != NULL)
  2064.       free (entry_parms.data.bytes); // if blob data was allocated, free it
  2065.  
  2066.    return; //  finished parsing that line
  2067. }
  2068.  
  2069.  
  2070. int main (int argc, char **argv)
  2071. {
  2072.    // program entrypoint
  2073.  
  2074.    typedef struct ifs_offsets_s
  2075.    {
  2076.       size_t startupheader;
  2077.       size_t startuptrailer;
  2078.       size_t imageheader;
  2079.       size_t imagedir;
  2080.       size_t imagetrailer;
  2081.    } ifs_offsets_t;
  2082.    typedef struct ifs_s
  2083.    {
  2084.       buffer_t data;
  2085.       ifs_offsets_t offsets;
  2086.       size_t final_size; // final size: not known (because not set) until everything has been written
  2087.    } ifs_t;
  2088.  
  2089.    startup_header_t startup_header = { 0 }; // output IFS's startup header
  2090.    startup_trailer_v2_t startup_trailer = { 0 }; // output IFS's startup trailer (version 2, with SHA-512 checksum and int32 checksum)
  2091.    image_header_t image_header = { 0 }; // output IFS's imagefs header
  2092.    image_trailer_v2_t image_trailer = { 0 }; // output IFS's imagefs trailer (version 2, with SHA-512 checksum and int32 checksum)
  2093.    fsentry_t *fsentries = NULL; // output IFS's filesystem entries
  2094.    size_t fsentry_count = 0; // number of entries in the IFS filesystem
  2095.    parms_t default_parms = { // default parameters for a filesystem entry
  2096.       .dperms = 0755,
  2097.       .perms = 0644,
  2098.       .mtime = UINT32_MAX,
  2099.       .mtime_for_inline_files = UINT32_MAX,
  2100.       .prefix = NULL, // will be initialized to a *mallocated* string: "/proc/boot"
  2101.       .should_follow_symlinks = true, // [+|-followlink]
  2102.       .should_autosymlink_dylib = true, // [+|-autolink]
  2103.    };
  2104.    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)
  2105.  
  2106.    uefi64_header_t *uefi_header = NULL;
  2107.    char path_on_buildhost[MAXPATHLEN] = "";
  2108.    char path_in_ifs[MAXPATHLEN] = "";
  2109.    const char *ifs_pathname = NULL;
  2110.    const char *rootdir_pathname = NULL;
  2111.    const fsentry_t *fsentry;
  2112.    void *reallocated_ptr;
  2113.    buffer_t compressed_imagefs;
  2114.    buffer_t opaqueblob_file;
  2115.    uint8_t *compressor_out;
  2116.    uint8_t *compressor_in;
  2117.    size_t compressor_outlen;
  2118.    size_t compressor_inlen;
  2119.    size_t reallocated_size;
  2120.    size_t available_space;
  2121.    size_t fsentry_index;
  2122.    size_t largest_index;
  2123.    size_t largest_size;
  2124.    size_t imgdir_size;
  2125.    size_t curr_offset;
  2126.    size_t remaining_len;
  2127.    ifs_t ifs = { 0 };
  2128.    int32_t checksum;
  2129.    char *first_pathname = NULL;
  2130.    char *second_pathname = NULL;
  2131.    char *third_pathname = NULL;
  2132.    char *sep;
  2133.    int arg_index;
  2134.    bool is_quoted_context = false;
  2135.    bool is_escaped_char = false;
  2136.    bool should_discard_inline_contents = false;
  2137.    bool want_info = false;
  2138.    bool want_everything = false;
  2139.    bool want_help = false;
  2140.    bool want_dump = false;
  2141.    bool want_strip = false;
  2142.    bool want_hexdump = false;
  2143.    bool hide_filename = false;
  2144.    bool is_foreign_endianness;
  2145.    int compressor_ret;
  2146.    FILE *buildfile_fp;
  2147.  
  2148.    // initialize stuff
  2149.    saved_ELF_sections = (char **) malloc (4 * sizeof (char *));
  2150.    ASSERT_WITH_ERRNO (saved_ELF_sections);
  2151.    saved_ELF_sections[0] = "QNX_info"; // NOTE: MUST BE THE FIRST ONE as we artificially shrink down the array to 1 when using it for boot ELF files
  2152.    saved_ELF_sections[1] = ".gnu_debuglink";
  2153.    saved_ELF_sections[2] = "QNX_usage";
  2154.    saved_ELF_sections[3] = ".note.gnu.build-id"; // undocumented by QNX, but nonetheless preserved
  2155.    saved_ELF_section_count = 4;
  2156.    default_parms.prefix = strdup ("/proc/boot");
  2157.    ASSERT_WITH_ERRNO (default_parms.prefix);
  2158.  
  2159.    // parse arguments
  2160.    for (arg_index = 1; arg_index < argc; arg_index++)
  2161.    {
  2162.       if ((strcmp (argv[arg_index], "--bootfile") == 0) && (arg_index + 1 < argc)) // --bootfile path/to/blob.bin
  2163.          bootfile_pathname = argv[++arg_index];
  2164.       else if ((strcmp (argv[arg_index], "--startupfile") == 0) && (arg_index + 1 < argc)) // --startupfile path/to/blob.bin@0x1030
  2165.       {
  2166.          sep = strchr (argv[++arg_index], '@');
  2167.          if ((sep == NULL) || (sep[1] == 0))
  2168.             DIE_WITH_EXITCODE (1, "the --startupfile arguments expects <pathname>@<entrypoint_from_image_base>");
  2169.          *sep = 0;
  2170.          startupfile_pathname = argv[arg_index];
  2171.          startupfile_ep_from_imagebase = (size_t) read_integer (sep + 1, 0);
  2172.       }
  2173.       else if ((strcmp (argv[arg_index], "-a") == 0) && (arg_index + 1 < argc)) // -a suffix
  2174.          sym_suffix = argv[++arg_index];
  2175.       else if (strcmp (argv[arg_index], "-n") == 0)
  2176.          default_parms.mtime_for_inline_files = 0; // inline files should have a mtime set to zero
  2177.       else if (strcmp (argv[arg_index], "-nn") == 0)
  2178.       {
  2179.          default_parms.mtime = 0; // *all* files should have a mtime set to zero
  2180.          default_parms.mtime_for_inline_files = 0;
  2181.       }
  2182.       else if ((strcmp (argv[arg_index], "--outdir") == 0) && (arg_index + 1 < argc)) // --outdir path
  2183.          second_pathname = argv[++arg_index];
  2184.       else if ((strcmp (argv[arg_index], "--outfile") == 0) && (arg_index + 1 < argc)) // --outfile pathname
  2185.          second_pathname = argv[++arg_index];
  2186.       else if (strcmp (argv[arg_index], "--info") == 0)
  2187.          want_info = true;
  2188.       else if (strcmp (argv[arg_index], "--dump") == 0)
  2189.          want_dump = true;
  2190.       else if (strcmp (argv[arg_index], "--hexdump") == 0) // voluntarily undocumented
  2191.          want_hexdump = true;
  2192.       else if (strcmp (argv[arg_index], "--strip") == 0)
  2193.          want_strip = true;
  2194.       else if (strcmp (argv[arg_index], "--everything") == 0)
  2195.          want_everything = true;
  2196.       else if (strcmp (argv[arg_index], "--hide-filename") == 0)
  2197.          hide_filename = true;
  2198.       else if (strncmp (argv[arg_index], "-v", 2) == 0) // -v[....]
  2199.          verbose_level += (int) strlen (argv[arg_index] + 1); // increase verbosity by the number of characters in this flag
  2200.       else if ((strcmp (argv[arg_index], "-l") == 0) && (arg_index + 1 < argc))
  2201.          arg_index++; // these args will be parsed once the build file is open
  2202.       else if ((strcmp (argv[arg_index], "-r") == 0) && (arg_index + 1 < argc))
  2203.       {
  2204.          reallocated_size = (SEARCH_PATH != NULL ? strlen (SEARCH_PATH) + 1 : 0) + strlen (argv[arg_index + 1]) + 1;
  2205.          reallocated_ptr = realloc (SEARCH_PATH, reallocated_size); // grow search prefixes array
  2206.          ASSERT_WITH_ERRNO (reallocated_ptr);
  2207.          if (SEARCH_PATH != NULL)
  2208.             strcat_s (reallocated_ptr, reallocated_size, PATH_SEP);
  2209.          strcat_s (reallocated_ptr, reallocated_size, argv[++arg_index]); // stack up another search prefix
  2210.          SEARCH_PATH = reallocated_ptr;
  2211.       }
  2212.       else if ((strcmp (argv[arg_index], "-s") == 0) && (arg_index + 1 < argc))
  2213.       {
  2214.          reallocated_ptr = realloc (saved_ELF_sections, (saved_ELF_section_count + 1) * sizeof (char *)); // grow ELF sections array
  2215.          ASSERT_WITH_ERRNO (reallocated_ptr);
  2216.          saved_ELF_sections = reallocated_ptr;
  2217.          saved_ELF_sections[saved_ELF_section_count++] = argv[++arg_index]; // stack up another ELF section name to preserve
  2218.       }
  2219.       else if ((strcmp (argv[arg_index], "-?") == 0) || (strcmp (argv[arg_index], "--help") == 0))
  2220.          want_help = true;
  2221.       else if ((first_pathname == NULL) && (*argv[arg_index] != '-'))
  2222.          first_pathname = argv[arg_index];
  2223.       else if ((second_pathname == NULL) && (*argv[arg_index] != '-'))
  2224.          second_pathname = argv[arg_index];
  2225.       else if ((third_pathname == NULL) && (*argv[arg_index] != '-'))
  2226.          third_pathname = argv[arg_index];
  2227.       else
  2228.          DIE_WITH_EXITCODE (1, "unrecognized option: '%s'", argv[arg_index]);
  2229.    }
  2230.  
  2231.    // do we want to display help ? (TODO: everything that's commented out is pending implementation)
  2232.    if (want_help)
  2233.    {
  2234.       FILE *out = (want_help ? stdout : stderr); // select the right output channel
  2235.       fprintf (out, "ifstool - QNX in-kernel filesystem creation utility by Pierre-Marie Baty <pm@pmbaty.com>\n");
  2236.       fprintf (out, "          version " VERSION_FMT_YYYYMMDD "\n", VERSION_ARG_YYYYMMDD);
  2237.       if (!want_help)
  2238.          fprintf (out, "error: missing parameters\n");
  2239.       fprintf (out, "usage:\n");
  2240.       fprintf (out, "    ifstool --info [--everything] [--hide-filename] <ifs file>\n");
  2241.       fprintf (out, "    ifstool --dump [--outdir <path>] <ifs file>\n");
  2242.       fprintf (out, "    ifstool --strip [--outfile <pathname>] <ELF file>\n");
  2243.       fprintf (out, "    ifstool [-?|--help]\n");
  2244.       // mkifs [-?] [-l inputline] [-n[n]] [-o directory] [-p patchfile] [-r rootdir] [-s section] [-v] [buildfile] [directory] [outputfile]
  2245.       fprintf (out, "    ifstool [--bootfile <pathname>] [--startupfile <pathname>@<EP_from_imgbase>] [--kerneloffs <fileoffs>] [-a suffix] [-l inputline] [-n[n]] [-r rootdir] [-s section] [-v[...]] [buildfile] [directory] [outputfile]\n");
  2246.       fprintf (out, "NOTE: the compiler mode requires predigested boot and startup files produced by mkifs.\n");
  2247.       fprintf (out, "options:\n");
  2248.       fprintf (out, "    -?       Display some help information.\n");
  2249.       fprintf (out, "    -a .ext  Append a suffix to symbol files generated via [+keeplinked].\n");
  2250.       fprintf (out, "    -l line  Process line before interpreting the buildfile. Input lines given\n");
  2251.       fprintf (out, "             to mkifs should be quoted to prevent interpretation by the shell\n");
  2252.       fprintf (out, "             (especially as mkifs input lines often contain spaces). Multiple\n");
  2253.       fprintf (out, "             -l options are processed in the order specified. No default.\n");
  2254.       fprintf (out, "    -n[n]    Force the modification times of all inline files to be 0. If you\n");
  2255.       fprintf (out, "             specify -nn, mkifs sets the modification times of all files to 0.\n");
  2256.       fprintf (out, "             When mkifs adds files to an IFS image, it uses the timestamp info\n");
  2257.       fprintf (out, "             from the file on the host machine. If mkifs is creating an inline\n");
  2258.       fprintf (out, "             file (which doesn't exist on the host machine), it must generate\n");
  2259.       fprintf (out, "             its own timestamp information. By default, it's the time at which\n");
  2260.       fprintf (out, "             the image is generated. This results in different checksum values\n");
  2261.       fprintf (out, "             for two identical builds, because the file's times are different.\n");
  2262.       fprintf (out, "             If you use -n, the checksum value is the same on all identical\n");
  2263.       fprintf (out, "             builds. The -nn option addresses a quirk in NTFS with daylight\n");
  2264.       fprintf (out, "             savings time. This forces the modification time for all files in\n");
  2265.       fprintf (out, "             the IFS image to be set to 0. This ensures that subsequent builds\n");
  2266.       fprintf (out, "             of the same IFS image have the same checksum.");
  2267. //      fprintf (out, "    -o dir   Specify a directory to be used for all permanent build artifacts,\n");
  2268. //      fprintf (out, "             other than the output image itself. The most common example is\n");
  2269. //      fprintf (out, "             the .sym files generated by the [+keeplinked] attribute.\n");
  2270. //      fprintf (out, "    -p file  Apply patching instructions from this file.\n");
  2271.       fprintf (out, "    -r dir   When searching for host files to be included in the image, search\n");
  2272.       fprintf (out, "             the default paths used for storing binaries within the specified\n");
  2273.       fprintf (out, "             directory before searching the default paths within $QNX_TARGET.\n");
  2274.       fprintf (out, "             You can define multiple -r options; each adds a set of paths to\n");
  2275.       fprintf (out, "             search for files. The -r options are evaluated from left to right\n");
  2276.       fprintf (out, "             meaning the paths prefixed with the first (leftmost) rootdir are\n");
  2277.       fprintf (out, "             searched first, then those prefixed with the second rootdir, and\n");
  2278.       fprintf (out, "             so on.\n");
  2279.       fprintf (out, "             Normally, mkifs searches any paths defined in $MKIFS_PATH when\n");
  2280.       fprintf (out, "             it was called and then the default paths within $QNX_TARGET. The\n");
  2281.       fprintf (out, "             default paths are based on the CPU architecture specified by\n");
  2282.       fprintf (out, "             $PROCESSOR and $PROCESSOR_BASE. If you specify -r options, mkifs\n");
  2283.       fprintf (out, "             searches the default paths prefixed with each dir variable before\n");
  2284.       fprintf (out, "             searching those within $QNX_TARGET. These paths are:\n");
  2285.       fprintf (out, "               dir/${PROCESSOR}/sbin\n");
  2286.       fprintf (out, "               dir/${PROCESSOR}/usr/sbin\n");
  2287.       fprintf (out, "               dir/${PROCESSOR}/boot/sys\n");
  2288.       fprintf (out, "               dir/${PROCESSOR_BASE}/boot/sys\n");
  2289.       fprintf (out, "               dir/${PROCESSOR}/bin\n");
  2290.       fprintf (out, "               dir/${PROCESSOR}/usr/bin\n");
  2291.       fprintf (out, "               dir/${PROCESSOR}/lib\n");
  2292.       fprintf (out, "               dir/${PROCESSOR}/lib/dll\n");
  2293.       fprintf (out, "               dir/${PROCESSOR}/usr/lib\n");
  2294.       fprintf (out, "             NOTE: The structure of the directory paths under dir must be\n");
  2295.       fprintf (out, "             identical to that of the default paths under $QNX_TARGET, but the\n");
  2296.       fprintf (out, "             root dir itself may be any path you choose. For example, if you\n");
  2297.       fprintf (out, "             wanted to include /scratch/aarch64le/sbin/devb-sata, you would\n");
  2298.       fprintf (out, "             specify a -r option like this:\n");
  2299.       fprintf (out, "               -r /scratch\n");
  2300.       fprintf (out, "             Note that you don't include $PROCESSOR or $PROCESSOR_BASE in dir.\n");
  2301.       fprintf (out, "    -s name  Don't strip the named section from ELF executables when creating\n");
  2302.       fprintf (out, "             an IFS image. You can use this option more than once to specify\n");
  2303.       fprintf (out, "             additional sections. By default, mkifs doesn't strip:\n");
  2304.       fprintf (out, "               .gnu_debuglink - the name and checksum of the debug info file\n");
  2305.       fprintf (out, "               QNX_info       - build properties\n");
  2306.       fprintf (out, "               QNX_usage      - usage message\n");
  2307.       fprintf (out, "             You can use the keepsection attribute to specify the sections\n");
  2308.       fprintf (out, "             that are not to be stripped from specific files in the image. For\n");
  2309.       fprintf (out, "             files in the bootstrap section (like startup or procnto), the\n");
  2310.       fprintf (out, "             global keepsection list affected by -s does not apply to these\n");
  2311.       fprintf (out, "             files. For them, only the QNX_info section is kept.\n");
  2312.       fprintf (out, "    -v[v..]  Operate verbosely. Specifying additional v options increases the\n");
  2313.       fprintf (out, "             verbosity.\n");
  2314.       exit (want_help ? 0 : 1);
  2315.    }
  2316.  
  2317.    // else do we want info about a particular IFS ? if so, dissecate it
  2318.    else if (want_info)
  2319.       exit (dump_ifs_info (first_pathname, want_everything, hide_filename));
  2320.  
  2321.    // else do we want to dump its contents ? if so, do so
  2322.    else if (want_dump)
  2323.       exit (dump_ifs_contents (first_pathname, (second_pathname != NULL ? second_pathname : ".")));
  2324.  
  2325.    // else do we want to hex dump a file ? (this is voluntarily undocumented)
  2326.    else if (want_hexdump)
  2327.       exit (dump_file_hex (first_pathname));
  2328.  
  2329.    // else do we want to strip an ELF file ? if so, do so
  2330.    else if (want_strip)
  2331.    {
  2332.       buffer_t file;
  2333.       ASSERT (Buffer_ReadFromFile (&file, first_pathname), "can't open \"%s\" for reading: %s", first_pathname, strerror (errno));
  2334.       ASSERT (Buffer_StripELFFile (&file, (const char **) saved_ELF_sections, saved_ELF_section_count, false, first_pathname), "error stripping \"%s\": %s", first_pathname, strerror (errno));
  2335.       ASSERT_WITH_ERRNO (Buffer_WriteToFile (&file, (second_pathname != NULL ? second_pathname : "<stdout>")));
  2336.       exit (0);
  2337.    }
  2338.  
  2339.    // we want to CREATE an IFS file
  2340.    buildfile_pathname = first_pathname; // assign the pathnames properly
  2341.    ifs_pathname = (third_pathname != NULL ? third_pathname : second_pathname); // this is some curious handling of cmdline args, but that's the way mkxfs does it
  2342.    rootdir_pathname = (third_pathname != NULL ? second_pathname : NULL);
  2343.  
  2344.    // make sure we have ${QNX_TARGET} pointing somewhere
  2345.    QNX_TARGET = getenv ("QNX_TARGET");
  2346.    if (QNX_TARGET == NULL)
  2347.       DIE_WITH_EXITCODE (1, "the QNX_TARGET environment variable is not set");
  2348.    else if (access (QNX_TARGET, 0) != 0)
  2349.       DIE_WITH_EXITCODE (1, "the QNX_TARGET environment variable doesn't point to an existing directory");
  2350.  
  2351.    // open build file
  2352.    if ((buildfile_pathname != NULL) && (strcmp (buildfile_pathname, "-") != 0))
  2353.    {
  2354.       fopen_s (&buildfile_fp, buildfile_pathname, "rb"); // open it
  2355.       if (buildfile_fp == NULL)
  2356.          DIE_WITH_EXITCODE (1, "unable to open build file \"%s\" for reading: %s", buildfile_pathname, strerror (errno));
  2357.    }
  2358.    else // no build file specified: use stdin
  2359.    {
  2360.       buildfile_pathname = "<stdin>";
  2361.       buildfile_fp = stdin;
  2362.    }
  2363.  
  2364.    // stack up filesystem entries
  2365.    memcpy (&entry_parms, &default_parms, sizeof (default_parms));
  2366.    entry_parms.st_mode = S_IFDIR | default_parms.dperms;
  2367.    add_fsentry (&fsentries, &fsentry_count, &entry_parms, "", NULL); // add the root dir first
  2368.  
  2369.    // parse -l arguments before everything else
  2370.    for (arg_index = 1; arg_index < argc; arg_index++)
  2371.       if ((strcmp (argv[arg_index], "-l") == 0) && (arg_index + 1 < argc))
  2372.          parse_line (NULL, argv[++arg_index], &fsentries, &fsentry_count, &default_parms);
  2373.  
  2374.    // parse the IFS build file line per line
  2375.    while (fgets (line_buffer, sizeof (line_buffer), buildfile_fp) != NULL)
  2376.    {
  2377.       if (current_line != NULL)
  2378.          free (current_line);
  2379.       current_line = strdup (line_buffer);
  2380.       ASSERT_WITH_ERRNO (current_line);
  2381.       lineno++; // keep track of current line number
  2382.       parse_line (buildfile_fp, line_buffer, &fsentries, &fsentry_count, &default_parms);
  2383.    }
  2384.  
  2385.    fclose (buildfile_fp); // finished parsing the build file
  2386.  
  2387.    // if a root dir was specified, open it as a directory and recursively add all of its contents to the filesystem
  2388.    if (rootdir_pathname != NULL)
  2389.       add_directory_contents_recursively (&fsentries, &fsentry_count, rootdir_pathname, strlen (rootdir_pathname), &default_parms);
  2390.  
  2391.    //////////////////////////////////
  2392.    // start constructing the IFS file
  2393.  
  2394.    Buffer_Initialize (&ifs.data);
  2395.  
  2396.    // do we have a startup file ? if so, this is a bootable image
  2397.    if (startupfile_pathname != NULL)
  2398.    {
  2399.       // write boot prefix
  2400.       if (boot_type == BOOTTYPE_UEFI) // UEFI boot
  2401.       {
  2402.          ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (&ifs.data, sizeof (uefi64_header_t))); // start by writing an empty UEFI header
  2403.          uefi_header = (uefi64_header_t *) ifs.data.bytes; // have a convenience pointer
  2404.          memcpy (&uefi_header->dos_header.signature, "MZ", 2); // store the MZ magic
  2405.          uefi_header->dos_header.bytes_in_last_page = 144; // fixed value
  2406.          uefi_header->dos_header.number_of_pages = 3; // fixed value
  2407.          uefi_header->dos_header.header_size_in_paragraphs = sizeof (uefi_header->dos_header) / 16;
  2408.          uefi_header->dos_header.requested_paragraphs = 0xffff;
  2409.          uefi_header->dos_header.initial_stack_pointer_value = 0x00b8; // fixed value
  2410.          uefi_header->dos_header.absolute_offset_to_relocation_table = sizeof (uefi_header->dos_header);
  2411.          uefi_header->dos_header.absolute_offset_to_pe_header = sizeof (uefi_header->dos_header) + sizeof (uefi_header->dos_stub_bytes);
  2412.          memcpy (uefi_header->dos_stub_bytes, "\x0E\x1F\xBA\x0E\x00\xB4\x09\xCD\x21\xB8\x01\x4C\xCD\x21" "This program cannot be run in DOS mode.\r\r\n" "\x24\x00\x00\x00\x00\x00\x00\x00", 64); // store DOS stub program
  2413.          memcpy (&uefi_header->pe_header.signature, "PE\0\0", 4); // store the PE magic
  2414.          uefi_header->pe_header.machine_type = (strcmp (image_processor, "x86_64") == 0 ? 0x8664 : (strcmp (image_processor, "aarch64le") == 0 ? 0xaa64 : 0xffff)); // store machine type
  2415.          uefi_header->pe_header.number_of_sections = 1; // store number of sections
  2416.          uefi_header->pe_header.epoch_timestamp = (uint32_t) time (NULL); // store timestamp (NOTE: mkifs doesn't obey its -nn arguments here, so neither shall we)
  2417.          uefi_header->pe_header.size_of_optional_header = sizeof (uefi_header->optional_header64); // 240 bytes
  2418.          uefi_header->pe_header.characteristics_bitmap = 0x0223; // store characteristics bitmap (executable, uses large addresses, relocs stripped, debug info stripped)
  2419.          memcpy (uefi_header->optional_header64.signature, "\x0b\x02", 2); // store the 64-bit optional header magic
  2420.          uefi_header->optional_header64.code_size = WILL_BE_FILLED_LATER; // total size of IFS minus 512 bytes for the UEFI boot header, i.e. size of startup blob plus size of image
  2421.          uefi_header->optional_header64.entrypoint_address = (uint32_t) startupfile_ep_from_imagebase;
  2422.          uefi_header->optional_header64.image_base = image_base;
  2423.          uefi_header->optional_header64.section_alignment = (uint32_t) image_pagesize;
  2424.          uefi_header->optional_header64.file_alignment = 512; // i.e. one filesystem block
  2425.          uefi_header->optional_header64.image_size = WILL_BE_FILLED_LATER; // total IFS file size
  2426.          uefi_header->optional_header64.size_of_headers = (uint32_t) bootfile_size;
  2427.          uefi_header->optional_header64.subsystem_type = 10; // IMAGE_SUBSYSTEM_EFI_APPLICATION
  2428.          uefi_header->optional_header64.stack_reserve_size = image_pagesize;
  2429.          uefi_header->optional_header64.stack_commit_size = image_pagesize;
  2430.          uefi_header->optional_header64.number_of_data_directories = 16; // mkifs reserves 16 data directories, filled with zeroes (FIXME: why?)
  2431.          memcpy (uefi_header->unique_section.section_name, "image\0\0\0", 8); // store the unique section name
  2432.          uefi_header->unique_section.virtual_size = WILL_BE_FILLED_LATER; // same as pe_image_optional_header64.code_size
  2433.          uefi_header->unique_section.virtual_address = ROUND_TO_UPPER_MULTIPLE (uefi_header->optional_header64.size_of_headers, uefi_header->optional_header64.file_alignment);
  2434.          uefi_header->unique_section.rawdata_size = WILL_BE_FILLED_LATER; // same as pe_image_optional_header64.code_size
  2435.          uefi_header->unique_section.rawdata_offset = uefi_header->unique_section.virtual_address;
  2436.          uefi_header->unique_section.characteristics_bitmap = 0x60; // image contains code + image contains initialized data
  2437.          ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (&ifs.data, uefi_header->optional_header64.size_of_headers)); // pad as necessary
  2438.       }
  2439.       else // BIOS boot
  2440.       {
  2441.          // ######################################################################################################################################################################################################################################
  2442.          // # FIXME: figure out how mkifs produces the boot prefix out of the BIOS "boot file" here
  2443.          // ######################################################################################################################################################################################################################################
  2444.          if (!Buffer_ReadFromFile (&opaqueblob_file, bootfile_pathname))
  2445.             DIE_WITH_EXITCODE (1, "failed to open \"%s\" for reading: %s", bootfile_pathname, strerror (errno));
  2446.          ASSERT_WITH_ERRNO (Buffer_AppendBuffer (&ifs.data, &opaqueblob_file)); // write boot blob
  2447.          Buffer_Forget (&opaqueblob_file);
  2448.          ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (&ifs.data, ROUND_TO_UPPER_MULTIPLE (ifs.data.size, image_align))); // pad as necessary
  2449.       }
  2450.  
  2451.       ifs.offsets.startupheader = ifs.data.size; // save startup header offset for future use
  2452.       memset (&startup_header, 0, sizeof (startup_header)); // prepare startup header
  2453.       memcpy (startup_header.signature, "\xeb\x7e\xff\x00", 4); // startup header signature, i.e. 0xff7eeb
  2454.       startup_header.version       = 1;
  2455.       startup_header.flags1        = STARTUP_HDR_FLAGS1_VIRTUAL | STARTUP_HDR_FLAGS1_TRAILER_V2 | startup_header_compression_flag; // flags, 0x21 (STARTUP_HDR_FLAGS1_VIRTUAL | STARTUP_HDR_FLAGS1_TRAILER_V2)
  2456.       startup_header.header_size   = sizeof (startup_header); // 256
  2457.       if (strcmp (image_processor, "x86_64") == 0)
  2458.          startup_header.machine = ELF_MACHINE_X86_64; // EM_X86_64
  2459.       else if (strcmp (image_processor, "aarch64le") == 0)
  2460.          startup_header.machine = ELF_MACHINE_AARCH64; // EM_AARCH64
  2461.       else
  2462.          DIE_WITH_EXITCODE (1, "unsupported processor type '%s' found in build file \"%s\"", image_processor, buildfile_pathname); // should not happen
  2463.       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.*")
  2464.       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)
  2465.       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)
  2466.       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)
  2467.       startup_header.startup_size  = WILL_BE_FILLED_LATER;                                  // [I ] Size of startup (never compressed), here 0x02f148 or 192 840 bytes
  2468.       startup_header.stored_size   = WILL_BE_FILLED_LATER;                                  // [I ] Size of entire image file (startup + *optionally compressed* imagefs) without optional boot prefix, here 0x00cd6128
  2469.       startup_header.imagefs_size  = WILL_BE_FILLED_LATER;                                  // [ S] Size of uncompressed imagefs, here 0x00ca6fe0 or 13 266 912 bytes
  2470.       startup_header.preboot_size  = (uint16_t) bootfile_size;                              // [I ] Size of loaded before header, here 0xf30 or 3888 bytes (size of "bios.boot" file))
  2471.       ASSERT_WITH_ERRNO (Buffer_Append (&ifs.data, &startup_header, sizeof (startup_header))); // write startup header
  2472.       ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (&ifs.data, ROUND_TO_UPPER_MULTIPLE (ifs.data.size, image_align))); // pad as necessary
  2473.  
  2474.       // ######################################################################################################################################################################################################################################
  2475.       // # FIXME: figure out how to re-create it:
  2476.       // first: open "startup-x86" ELF file,
  2477.       //        lookup section headers table (there is no program headers table in this one)
  2478.       //        FIXME: figure out something in there where the result is 0x1401030 !!!
  2479.       // 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
  2480.       // then: parse resulting ELF file, take all program segments and concatenate them --> this is the blob (FIXME: wrong?)
  2481.       // ######################################################################################################################################################################################################################################
  2482. #if 0 // nonworking
  2483.       // <deleted>
  2484. #else // working
  2485.       if (!Buffer_ReadFromFile (&opaqueblob_file, startupfile_pathname))
  2486.          DIE_WITH_EXITCODE (1, "failed to open \"%s\" for reading: %s", startupfile_pathname, strerror (errno));
  2487.       ASSERT_WITH_ERRNO (Buffer_AppendBuffer (&ifs.data, &opaqueblob_file)); // write startup blob
  2488.       Buffer_Forget (&opaqueblob_file);
  2489. #endif // working
  2490.       ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (&ifs.data, ROUND_TO_UPPER_MULTIPLE (ifs.data.size, image_align))); // pad as necessary
  2491.  
  2492.       ifs.offsets.startuptrailer = ifs.data.size; // save startup trailer offset for future use
  2493.       ASSERT_WITH_ERRNO (Buffer_Append (&ifs.data, &startup_trailer, sizeof (startup_trailer))); // write startup trailer
  2494.       ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (&ifs.data, ROUND_TO_UPPER_MULTIPLE (ifs.data.size, image_align))); // pad as necessary
  2495.    }
  2496.  
  2497.    ifs.offsets.imageheader = ifs.data.size; // save image header offset for future use
  2498.    memset (&image_header, 0, sizeof (image_header)); // prepare image header
  2499.    memcpy (&image_header.signature, "imagefs", 7); // image filesystem signature, i.e. "imagefs"
  2500.    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)
  2501.    image_header.image_size    = WILL_BE_FILLED_LATER; // size from header to end of trailer (here 0xca6fe0 or 13 266 912)
  2502.    image_header.hdr_dir_size  = WILL_BE_FILLED_LATER; // size from header to last dirent (here 0x12b8 or 4792)
  2503.    image_header.dir_offset    = sizeof (image_header); // offset from header to first dirent (here 0x5c or 92)
  2504.    image_header.boot_ino[0]   = image_kernel_ino; // inode of files for bootstrap p[ro?]g[ra?]ms (here 0xa0000002, 0, 0, 0)
  2505.    image_header.script_ino    = image_bootscript_ino; // inode of file for script (here 3)
  2506.    image_header.mountpoint[0] = '/'; // default mountpoint for image ("/" + "\0\0\0")
  2507.    ASSERT_WITH_ERRNO (Buffer_Append (&ifs.data, &image_header, sizeof (image_header))); // write image header
  2508.    ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (&ifs.data, ROUND_TO_UPPER_MULTIPLE (ifs.data.size, image_align))); // pad as necessary
  2509.  
  2510.    // write image directory (with the wrong file offsets)
  2511.    ifs.offsets.imagedir = ifs.data.size; // save image directory offset for future use
  2512.    curr_offset = ifs.offsets.imagedir;
  2513.    for (fsentry_index = 0; fsentry_index < fsentry_count; fsentry_index++)
  2514.    {
  2515.       Buffer_WriteIFSDirectoryEntryAt (&ifs.data, curr_offset, &fsentries[fsentry_index]); // write each dirent (the unknown fields will be fixed later)
  2516.       curr_offset += fsentries[fsentry_index].header.size; // advance to the next one
  2517.    }
  2518.    ASSERT_WITH_ERRNO (Buffer_AppendByteArray (&ifs.data, "\0\0\0\0")); // there seems to be 4 bytes of padding after the image directory
  2519.    imgdir_size = ifs.data.size - ifs.offsets.imagedir; // measure image dir size and save it for future use
  2520.  
  2521.    // is it a bootable image with a startup file ?
  2522.    if (startupfile_pathname != NULL)
  2523.    {
  2524.       // compute the kernel offset: address of the first page that comes after the directory entries
  2525.       kernelfile_offset = ROUND_TO_UPPER_MULTIPLE (ifs.data.size, image_pagesize);
  2526.  
  2527.       // write the filesystem entries that may fit before the kernel
  2528.       for (;;)
  2529.       {
  2530.          available_space = kernelfile_offset - ifs.data.size; // measure the available space until the kernel
  2531.  
  2532.          // look for the biggest one that can fit
  2533.          largest_index = 0;
  2534.          largest_size = 0;
  2535.          for (fsentry_index = 1; fsentry_index < fsentry_count; fsentry_index++)
  2536.          {
  2537.             if (!S_ISREG (fsentries[fsentry_index].header.mode) || fsentries[fsentry_index].UNSAVED_was_data_written || (fsentries[fsentry_index].u.file.size > available_space))
  2538.                continue; // skip all entries that don't have a separate data block, those who were written already and those that wouldn't fit
  2539.             if (fsentries[fsentry_index].u.file.size > largest_size)
  2540.             {
  2541.                largest_size = fsentries[fsentry_index].u.file.size;
  2542.                largest_index = fsentry_index;
  2543.             }
  2544.          }
  2545.          if (largest_size == 0)
  2546.             break; // found none ? if so, stop searching
  2547.          fsentry_index = largest_index;
  2548.  
  2549.          fsentries[fsentry_index].u.file.offset = (uint32_t) (ifs.data.size - ifs.offsets.imageheader); // save file data blob offset in file structure
  2550.          Buffer_AppendIFSFileData (&ifs.data, &fsentries[fsentry_index]); // write file data
  2551.          fsentries[fsentry_index].UNSAVED_was_data_written = true; // and remember this file's data was written
  2552.       }
  2553.       LOG_INFO ("Last written offset: 0x%zx", ifs.data.size);
  2554.       LOG_INFO ("Kernel file offset: 0x%zx", kernelfile_offset);
  2555.       ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (&ifs.data, kernelfile_offset)); // reach the kernel offset
  2556.  
  2557.       // now write the QNX kernel
  2558.       for (fsentry_index = 1; fsentry_index < fsentry_count; fsentry_index++)
  2559.          if (fsentries[fsentry_index].header.ino == image_kernel_ino)
  2560.             break; // locate the kernel directory entry (can't fail)
  2561.       fsentries[fsentry_index].u.file.offset = (uint32_t) (ifs.data.size - ifs.offsets.imageheader); // save file data blob offset in file structure
  2562.       Buffer_AppendIFSFileData (&ifs.data, &fsentries[fsentry_index]); // write kernel file data
  2563.       fsentries[fsentry_index].UNSAVED_was_data_written = true; // and remember this file's data was written
  2564.    }
  2565.  
  2566.    // then write all the other files by increasing inode number: ELF files first
  2567.    for (fsentry_index = 1; fsentry_index < fsentry_count; fsentry_index++)
  2568.    {
  2569.       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
  2570.           || (fsentries[fsentry_index].u.file.size < 4) || (memcmp (fsentries[fsentry_index].UNSAVED_databuf, ELF_MAGIC_STR, 4) != 0)) // filter out anything that's not an ELF file
  2571.          continue; // skip all entries that don't have a separate data block and those who were written already
  2572.       fsentries[fsentry_index].u.file.offset = (uint32_t) (ifs.data.size - ifs.offsets.imageheader); // save file data blob offset in file structure
  2573.       Buffer_AppendIFSFileData (&ifs.data, &fsentries[fsentry_index]); // write file data
  2574.       fsentries[fsentry_index].UNSAVED_was_data_written = true; // and remember this file's data was written
  2575.    }
  2576.    // other files (non-ELF, e.g. scripts and data files) last, in decreasing size order
  2577.    for (;;)
  2578.    {
  2579.       // look for the biggest one that can fit
  2580.       largest_index = 0;
  2581.       largest_size = 0;
  2582.       for (fsentry_index = 1; fsentry_index < fsentry_count; fsentry_index++)
  2583.       {
  2584.          if (!S_ISREG (fsentries[fsentry_index].header.mode) || fsentries[fsentry_index].UNSAVED_was_data_written)
  2585.             continue; // skip all entries that don't have a separate data block, those who were written already and those that wouldn't fit
  2586.          if (fsentries[fsentry_index].u.file.size > largest_size)
  2587.          {
  2588.             largest_size = fsentries[fsentry_index].u.file.size;
  2589.             largest_index = fsentry_index;
  2590.          }
  2591.       }
  2592.       if (largest_size == 0)
  2593.          break; // found none ? if so, stop searching
  2594.       fsentry_index = largest_index;
  2595.  
  2596.       fsentries[fsentry_index].u.file.offset = (uint32_t) (ifs.data.size - ifs.offsets.imageheader); // save file data blob offset in file structure
  2597.       Buffer_AppendIFSFileData (&ifs.data, &fsentries[fsentry_index]); // write file data
  2598.       fsentries[fsentry_index].UNSAVED_was_data_written = true; // and remember this file's data was written
  2599.    }
  2600.    ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (&ifs.data, ROUND_TO_UPPER_MULTIPLE (ifs.data.size, image_align))); // pad as necessary
  2601.  
  2602.    // finally, write trailer (including empty checksum)
  2603.    ifs.offsets.imagetrailer = ifs.data.size; // save image trailer offset for future use
  2604.    ASSERT_WITH_ERRNO (Buffer_Append (&ifs.data, &image_trailer, sizeof (image_trailer))); // write image trailer
  2605.    ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (&ifs.data, ROUND_TO_UPPER_MULTIPLE (ifs.data.size, image_align))); // pad as necessary
  2606.  
  2607.    // if we need to pad it to a specific length, do so
  2608.    ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (&ifs.data, image_totalsize));
  2609.    ifs.final_size = ifs.data.size; // and this is the final size of the IFS
  2610.  
  2611.    // see if we are past the image max size, in which case it's an error
  2612.    if (ifs.final_size > image_maxsize)
  2613.       DIE_WITH_EXITCODE (1, "image file size %zd exceeds max size (%zd)", ifs.final_size, (size_t) image_maxsize);
  2614.  
  2615.    // do we have a startup file ? if so, this is a bootable image
  2616.    if (startupfile_pathname != NULL)
  2617.    {
  2618.       // patch the startup header with its final values
  2619.       startup_header.startup_size = (uint32_t) (ifs.offsets.imageheader - ifs.offsets.startupheader); // size of startup header up to image header
  2620.       startup_header.imagefs_size = (uint32_t) (ifs.final_size - ifs.offsets.imageheader); // size of uncompressed imagefs
  2621.       startup_header.ram_size     = (uint32_t) (ifs.final_size - ifs.offsets.startupheader);
  2622.       startup_header.stored_size  = (uint32_t) (ifs.final_size - ifs.offsets.startupheader);
  2623.       ASSERT_WITH_ERRNO (Buffer_WriteAt (&ifs.data, ifs.offsets.startupheader, &startup_header, sizeof (startup_header))); // write the final startup header at its right offset
  2624.    }
  2625.  
  2626.    // rewrite image header with final values
  2627.    image_header.image_size = (uint32_t) (ifs.final_size - ifs.offsets.imageheader); // size of uncompressed imagefs
  2628.    image_header.hdr_dir_size = sizeof (image_header) + (uint32_t) imgdir_size; // size from start of image header to last dirent
  2629.    ASSERT_WITH_ERRNO (Buffer_WriteAt (&ifs.data, ifs.offsets.imageheader, &image_header, sizeof (image_header))); // write image header
  2630.  
  2631.    // rewrite image directory with final offset values
  2632.    if (image_header.flags & IMAGE_FLAGS_SORTED)
  2633.       qsort (&fsentries[1], fsentry_count - 1, sizeof (fsentry_t), fsentry_compare_pathnames_cb); // sort the filesystem entries by pathname if necessary
  2634.    curr_offset = ifs.offsets.imagedir; // position ourselves at the beginning of the image directory
  2635.    for (fsentry_index = 0; fsentry_index < fsentry_count; fsentry_index++)
  2636.    {
  2637.       Buffer_WriteIFSDirectoryEntryAt (&ifs.data, curr_offset, &fsentries[fsentry_index]); // rewrite each dirent
  2638.       curr_offset += fsentries[fsentry_index].header.size; // advance to the next one
  2639.    }
  2640.  
  2641.    // ALL CHECKSUMS AT THE VERY END
  2642.  
  2643.    // compute SHA-512 checksum and V1 checksum of image block
  2644.    if (   ( (image_header.flags & IMAGE_FLAGS_BIGENDIAN) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
  2645.        || (!(image_header.flags & IMAGE_FLAGS_BIGENDIAN) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__   )))
  2646.       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
  2647.    else
  2648.       is_foreign_endianness = false; // else this header is for the same endianness as us
  2649.  
  2650.    if (image_header.flags & IMAGE_FLAGS_TRAILER_V2) // is it a V2 trailer ?
  2651.    {
  2652.       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
  2653.       checksum = update_checksum (&ifs.data.bytes[ifs.offsets.imageheader], ifs.offsets.imagetrailer + SHA512_DIGEST_LENGTH - ifs.offsets.imageheader, is_foreign_endianness); // compute old checksum
  2654.       memcpy (&ifs.data.bytes[ifs.offsets.imagetrailer + SHA512_DIGEST_LENGTH], &checksum, 4); // and write it in place
  2655.    }
  2656.    else // old V1 trailer
  2657.    {
  2658.       checksum = update_checksum (&ifs.data.bytes[ifs.offsets.imageheader], ifs.offsets.imagetrailer - ifs.offsets.imageheader, is_foreign_endianness); // compute old checksum
  2659.       memcpy (&ifs.data.bytes[ifs.offsets.imagetrailer], &checksum, 4); // and write it in place
  2660.    }
  2661.  
  2662.    // should we compress the image block ?
  2663.    if (startup_header_compression_flag != STARTUP_HDR_FLAGS1_COMPRESS_NONE)
  2664.    {
  2665.       // it appears mkifs compresses data in blocks, prefixed by 2-byte block size in BIG ENDIAN
  2666.       Buffer_InitWithSize (&compressed_imagefs, image_header.image_size * 11 / 10); // mallocate and add 10% for safety
  2667.       compressed_imagefs.size = 0;
  2668.       compressor_in = &ifs.data.bytes[ifs.offsets.imageheader]; // point at the start of the data to compress
  2669.       compressor_out = &compressed_imagefs.bytes[2]; // point after the compressed block size word
  2670.       remaining_len = ifs.data.size - ifs.offsets.imageheader; // see how many bytes there are to compress
  2671.  
  2672.       if (startup_header_compression_flag == STARTUP_HDR_FLAGS1_COMPRESS_UCL)
  2673.          ASSERT (ucl_init () == UCL_E_OK, "UCL library initialization failed -- please recompile this tool with less aggressive optimizations");
  2674.       else if (startup_header_compression_flag == STARTUP_HDR_FLAGS1_COMPRESS_LZO)
  2675.          ASSERT (ucl_init () == UCL_E_OK, "LZO library initialization failed -- please recompile this tool with less aggressive optimizations");
  2676.       else if (startup_header_compression_flag == STARTUP_HDR_FLAGS1_COMPRESS_ZLIB)
  2677.          DIE_WITH_EXITCODE (1, "unimplemented compression scheme: zlib (FIXME)");
  2678.       else
  2679.          DIE_WITH_EXITCODE (1, "unsupported compression flags: 0x%2x", startup_header_compression_flag);
  2680.    
  2681.       // run the compressible payload (the imagefs) through the right compression algorithm
  2682.       while (remaining_len > 0)
  2683.       {
  2684.          compressor_inlen = (ucl_uint) remaining_len; // size the compressor input appropriately
  2685.          if (compressor_inlen > 65536)
  2686.             compressor_inlen = 65536; // cap it to a VERY conservative value
  2687.          if (startup_header_compression_flag == STARTUP_HDR_FLAGS1_COMPRESS_UCL)
  2688.          {
  2689.             // UCL compression. NOTE: the decompressor function used in startup-x86 is "ucl_nrv2b_decompress_8". Also compression level is hardcoded at 9 in mkifs, but can range from 1 to 10
  2690.             static ucl_uint ucl_outlen; // have a different variable because of pointer size mismatch
  2691.             while (((compressor_ret = ucl_nrv2b_99_compress (compressor_in, (ucl_uint) compressor_inlen, compressor_out, &ucl_outlen, NULL, 10, NULL, NULL)) == UCL_E_OK) && (ucl_outlen > 0xFFFF))
  2692.                compressor_inlen -= 4096; // as long as we can't produce compressed blocks whose size can fit in 2 bytes, try again with 4 Kb input less (this is hardcoded in mkifs, thus not CPU-specific)
  2693.             ASSERT (compressor_ret == UCL_E_OK, "UCL compression error: ucl_nrv2b_99_compress() returned %d", compressor_ret); // make sure it's not a compression error
  2694.             compressor_outlen = ucl_outlen; // cast back to size_t
  2695.          }
  2696.          else if (startup_header_compression_flag == STARTUP_HDR_FLAGS1_COMPRESS_LZO)
  2697.          {
  2698.             // LZO compression. NOTE: the compressor function used in mkifs is "lzo1x_999_compress" which is from the full LZO package... I use use minilzo, but I have to admit the compression ratio of the full LZO is *much* better.
  2699.             static lzo_align_t lzo_workmem[(LZO1X_1_MEM_COMPRESS + (sizeof (lzo_align_t) - 1)) / sizeof(lzo_align_t)]; // heap-allocated aligned buffer
  2700.             static lzo_uint lzo_outlen; // have a different variable because of pointer size mismatch
  2701.             while (((compressor_ret = lzo1x_1_compress (compressor_in, compressor_inlen, compressor_out, &lzo_outlen, lzo_workmem)) == UCL_E_OK) && (lzo_outlen > 0xFFFF))
  2702.                compressor_inlen -= 4096; // as long as we can't produce compressed blocks whose size can fit in 2 bytes, try again with 4 Kb input less (this is hardcoded in mkifs, thus not CPU-specific)
  2703.             ASSERT (compressor_ret == LZO_E_OK, "LZO compression error: lzo1x_1_compress() returned %d", compressor_ret); // make sure it's not a compression error
  2704.             compressor_outlen = lzo_outlen; // cast back to size_t
  2705.          }
  2706.          else if (startup_header_compression_flag == STARTUP_HDR_FLAGS1_COMPRESS_ZLIB)
  2707.          {
  2708.             // Zlib (TODO)
  2709.          }
  2710.  
  2711.          // the compression produced a block smaller than 65536 bytes
  2712.          //LOG_DEBUG ("compressed block size %zd", compressor_outlen);
  2713.          compressed_imagefs.bytes[compressed_imagefs.size + 0] = (uint8_t) (compressor_outlen >> 8); // write compressed block size word (in big endian)
  2714.          compressed_imagefs.bytes[compressed_imagefs.size + 1] = (uint8_t) (compressor_outlen >> 0);
  2715.          compressed_imagefs.size += 2 + (size_t) compressor_outlen; // advance in compression buffer by the compressed block size word plus the compressed block size
  2716.  
  2717.          remaining_len -= compressor_inlen; // see how many bytes remain to compress
  2718.  
  2719.          compressor_in += compressor_inlen; // advance in input stream
  2720.          compressor_out += 2 + compressor_outlen; // advance in output stream
  2721.       }
  2722.  
  2723.       compressed_imagefs.bytes[compressed_imagefs.size + 0] = 0; // write the end of stream marker (empty block with nil size)
  2724.       compressed_imagefs.bytes[compressed_imagefs.size + 1] = 0;
  2725.       compressed_imagefs.size += 2;
  2726.       LOG_INFO ("compressed %zd bytes into %zd bytes\n", ifs.data.size - ifs.offsets.imageheader, compressed_imagefs.size);
  2727.  
  2728.       /////////////////////////////////////////////////////////////
  2729.       // WARNING: ALL IFS OFFSETS BECOME INVALID PAST THIS POINT //
  2730.       /////////////////////////////////////////////////////////////
  2731.  
  2732.       // now place the compressed buffer in the payload at the imagefs offset
  2733.       ASSERT_WITH_ERRNO (Buffer_WriteBufferAt (&ifs.data, ifs.offsets.imageheader, &compressed_imagefs));
  2734.       ifs.data.size = ifs.offsets.imageheader + compressed_imagefs.size; // update IFS data size
  2735.       Buffer_Forget (&compressed_imagefs);
  2736.  
  2737.       // fix the stored size in the startup header
  2738.       startup_header.stored_size = (uint32_t) (ifs.data.size - ifs.offsets.startupheader);
  2739.       ASSERT_WITH_ERRNO (Buffer_WriteAt (&ifs.data, ifs.offsets.startupheader, &startup_header, sizeof (startup_header))); // write the final startup header at its right offset
  2740.    }
  2741.  
  2742.    // do we have a startup file ? if so, this is a bootable image
  2743.    if (startupfile_pathname != NULL)
  2744.    {
  2745.       if (boot_type == BOOTTYPE_UEFI) // UEFI boot: fix the final offsets and sizes in the EFI executable's PE header
  2746.       {
  2747.          uefi_header = (uefi64_header_t *) ifs.data.bytes; // have a convenience pointer
  2748.          uefi_header->optional_header64.code_size = (uint32_t) (ifs.data.size - ifs.offsets.startupheader); // total size of IFS minus 512 bytes for the UEFI boot header, i.e. size of startup blob plus size of image
  2749.          uefi_header->optional_header64.image_size = (uint32_t) ifs.data.size; // total IFS file size
  2750.          uefi_header->unique_section.virtual_size = uefi_header->optional_header64.code_size; // same as pe_image_optional_header64.code_size
  2751.          uefi_header->unique_section.rawdata_size = uefi_header->optional_header64.code_size; // same as pe_image_optional_header64.code_size
  2752.       }
  2753.  
  2754.       // compute SHA-512 checksum and V1 checksum of startup block
  2755.       if (   ( (startup_header.flags1 & STARTUP_HDR_FLAGS1_BIGENDIAN) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
  2756.           || (!(startup_header.flags1 & STARTUP_HDR_FLAGS1_BIGENDIAN) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__   )))
  2757.          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
  2758.       else
  2759.          is_foreign_endianness = false; // else this header is for the same endianness as us
  2760.  
  2761.       if (startup_header.flags1 & STARTUP_HDR_FLAGS1_TRAILER_V2) // is it a V2 trailer ?
  2762.       {
  2763.          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
  2764.          checksum = update_checksum (&ifs.data.bytes[ifs.offsets.startupheader], ifs.offsets.startuptrailer + SHA512_DIGEST_LENGTH - ifs.offsets.startupheader, is_foreign_endianness); // compute old checksum
  2765.          memcpy (&ifs.data.bytes[ifs.offsets.startuptrailer + SHA512_DIGEST_LENGTH], &checksum, 4); // and write it in place
  2766.       }
  2767.       else // old V1 trailer
  2768.       {
  2769.          checksum = update_checksum (&ifs.data.bytes[ifs.offsets.startupheader], ifs.offsets.startuptrailer - ifs.offsets.startupheader, is_foreign_endianness); // compute old checksum
  2770.          memcpy (&ifs.data.bytes[ifs.offsets.startuptrailer], &checksum, 4); // and write it in place
  2771.       }
  2772.    }
  2773.  
  2774.    // now rewrite IFS with the correct checksums
  2775.    ASSERT_WITH_ERRNO (Buffer_WriteToFile (&ifs.data, (ifs_pathname != NULL ? ifs_pathname : "<stdout>")));
  2776.  
  2777.    // finished, cleanup
  2778.    for (fsentry_index = 0; fsentry_index < fsentry_count; fsentry_index++)
  2779.    {
  2780.       fsentry = &fsentries[fsentry_index]; // quick access to filesystem entry
  2781.       if (S_ISDIR (fsentry->header.mode))
  2782.          free (fsentry->u.dir.path);
  2783.       else if (S_ISLNK (fsentry->header.mode))
  2784.       {
  2785.          free (fsentry->u.symlink.path);
  2786.          free (fsentry->u.symlink.contents);
  2787.       }
  2788.       else if (S_ISREG (fsentry->header.mode))
  2789.       {
  2790.          free (fsentry->u.file.path);
  2791.          free (fsentry->UNSAVED_databuf);
  2792.       }
  2793.       else if (S_ISFIFO (fsentry->header.mode))
  2794.          free (fsentry->u.device.path);
  2795.    }
  2796.  
  2797.    // and exit with a success code
  2798.    LOG_INFO ("Success");
  2799.    exit (0);
  2800. }
  2801.