// ifstool.c -- portable reimplementation of QNX's mkifs by Pierre-Marie Baty <pm@pmbaty.com>
 
 
 
// TODO: preboot file stripping
 
// TODO: startup file stripping
 
// TODO: kernel file stripping
 
// TODO: boot script compiler
 
 
 
// standard C includes
 
#include <stdint.h>
 
#include <stdbool.h>
 
#include <stdlib.h>
 
#include <stdarg.h>
 
#include <stdio.h>
 
#include <string.h>
 
#include <limits.h>
 
#include <errno.h>
 
#include <sys/stat.h>
 
#include <ctype.h>
 
#include <time.h>
 
 
 
// platform-specific includes
 
#ifdef _MSC_VER
 
#include <io.h>
 
#include <direct.h>
 
#include <sys/utime.h>
 
#else // !_MSC_VER
 
#include <sys/param.h>
 
#include <unistd.h>
 
#include <utime.h>
 
#endif // _MSC_VER
 
 
 
// own includes
 
#include "buffer.h"
 
#include "sha512.h"
 
#include "elffile.h"
 
#include "ifsfile.h"
 
#include "utility.h"
 
 
 
 
 
// compiler-specific glue
 
#ifndef _MSC_VER
 
#define sscanf_s sscanf // WARNING: TRUE FOR THIS FILE ONLY!
 
#endif // !_MSC_VER
 
 
 
 
 
// 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
 
const char *__asan_default_options () { return ("detect_leaks=0"); }
 
 
 
 
 
// placeholder value
 
#define WILL_BE_FILLED_LATER 0xbaadf00d // urgh
 
 
 
 
 
// miscellaneous macros
 
#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
 
#ifdef _WIN32
 
#define IS_DIRSEP(c) (((c) == '/') || ((c) == '\\')) // platform-specific directory separator, Win32 variant
 
#define PATH_SEP ";" // platform-specific PATH element separator (as string), Win32 variant
 
#else // !_WIN32, thus POSIX
 
#define IS_DIRSEP(c) ((c) == '/') // platform-specific directory separator, UNIX variant
 
#define PATH_SEP ":" // platform-specific PATH element separator (as string), UNIX variant
 
#endif // _WIN32
 
#define RECORD_SEP "\x1e" // arbitrarily-chosen ASCII record separator, as a C string suitable for e.g. strtok()
 
 
 
 
 
#define INITIAL_STARTUP_SCRIPT \
 
   /* procmgr_symlink /proc/boot/ldqnx-64.so.2 /usr/lib/ldqnx-64.so.2 */ \
 
   "\x34\x00" /*size*/ "\x04" /*type*/ "\x00" /*spare*/ "/proc/boot/ldqnx-64.so.2\0" "/usr/lib/ldqnx-64.so.2\0" \
 
   /* sh /proc/boot/startup.sh */ \
 
   "\x88\x00" /*size*/ "\x00" /*type*/ "\x00" /*spare*/ "\x00" /*CPU mask*/ "\x00" /*flags*/ "\x00\x00" /*reserved*/ "\x00" /*policy*/ "\x00" /*priority*/ "\02" /*argc*/ "\x02" /*envc*/ "sh\0" /*executable*/ "sh\0" "/proc/boot/startup.sh\0" /*argv*/ "PATH=/sbin:/usr/sbin:/bin:/usr/bin:/proc/boot\0" "LD_LIBRARY_PATH=/proc/boot:/lib:/lib/dll:/usr/lib\0" /*envp*/ \
 
   /* display_msg "Startup complete */ \
 
   "\x18\x00" /*size*/ "\x03" /*type*/ "\x00" /*spare*/ "Startup complete\n\0" "\x00\00" /*padding*/ \
 
   /* trailer */ \
 
   "\x00\x00\x00\x00"
 
 
 
 
 
// IFS directory entry insertion parameters structure type definition
 
typedef struct parms_s
 
{
 
   int dperms; // directory permissions (e.g. 0755)
 
   int perms; // file permissions (e.g. 0644)
 
   int uid; // owner user ID (e.g. 0 = root)
 
   int gid; // owner group ID (e.g. 0 = root)
 
   int st_mode; // entry type (e.g. S_IFREG for files) and permissions
 
   uint32_t mtime; // entry's modification time POSIX timestamp - set to UINT32_MAX to use the concerned files' mtime on the build host
 
   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)
 
   char *prefix; // [prefix=path] install path (e.g. "proc/boot")
 
   bool should_follow_symlinks; // follow symlinks
 
   bool should_autosymlink_dylib; // dynamic libraries should be written under their official SONAME and a named symlink be created pointing at them
 
   bool should_keep_ld_output; // whether to keep .sym files produced by ld calls, togglable by the [+keeplinked] attribute
 
   bool is_compiled_bootscript; // entry has [+script] attribute
 
   int extra_ino_flags; // bitmap of extra inode flags (IFS_INO_xxx)
 
   char *search; // [search=path[:path]] binary search path (the default one will be constructed at startup)
 
 
 
   buffer_t data; // the resolved file's own data bytes
 
} parms_t;
 
 
 
 
 
// exported globals
 
int verbose_level = 1; // verbosity level, can be increased with multiple -v[...] flags
 
 
 
 
 
// global variables used in this module only
 
static char line_buffer[4096]; // scrap buffer for the IFS build file parser
 
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
 
static uint32_t image_end = UINT32_MAX; // default image end (no limit)
 
static uint32_t image_maxsize = UINT32_MAX; // default image max size (no limit)
 
static uint32_t image_totalsize = 0; // image total size, measured once all the blocks have been written to the output IFS file
 
static uint32_t image_align = 4; // default image alignment, as per QNX docs
 
static uint32_t image_kernel_ino = 0;
 
static uint32_t image_bootscript_ino = 0;
 
#if defined(__x86_64__)
 
static char image_processor[16] = "x86_64"; // default CPU type for which this image is built, either "x86_64" or "aarch64le" (will be used to find out the right include paths under $QNX_TARGET)
 
static char image_processor_base[16] = "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)
 
#elif defined(__aarch64__)
 
static char image_processor[16] = "aarch64le"; // default CPU type for which this image is built, either "x86_64" or "aarch64le" (will be used to find out the right include paths under $QNX_TARGET)
 
static char image_processor_base[16] = "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)
 
#else // unknown platform
 
#error Please port ifstool to this platform
 
#endif
 
static char *buildfile_pathname = NULL; // pathname of IFS build file
 
static char *current_line = NULL; // copy of current line in IFS build file
 
static int lineno = 0; // current line number in IFS build file
 
static char *QNX_TARGET = NULL; // value of the $QNX_TARGET environment variable
 
static char *SEARCH_PATH = NULL; // mallocated string of search paths, populated by the -r command-line argument
 
static char **saved_ELF_sections = NULL; // mallocated array of const strings, populated by the -s command-line argument
 
static size_t saved_ELF_section_count = 0; // number of elements in the saved_ELF_sections array
 
 
 
// bootable IFS support
 
static char *bootfile_pathname = NULL;           // HACK: pathname to bootcode binary blob file to put at the start of a bootable IFS
 
static size_t bootfile_size = 0;                 // HACK: size of the bootcode binary blob file to put at the start of a bootable IFS
 
static char *startupfile_pathname = NULL;        // HACK: pathname to precompiled startup file blob to put in the startup header of a bootable IFS
 
static size_t startupfile_ep_from_imagebase = 0; // HACK: startup code entrypoint offset from image base for a bootable IFS 
 
static char *kernelfile_pathname = NULL;         // HACK: pathname to precompiled kernel file blob to put in a bootable IFS
 
static size_t kernelfile_offset = 0;             // HACK: kernel file offset in bootable IFS
 
 
 
 
 
// exported function prototypes
 
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
 
 
 
 
 
// prototypes of local functions
 
static long long read_integer (const char *str); // reads an integer number for a string that may be specified in either hex, octal or decimal base, and may have an optional unit suffix (k, m, g, t)
 
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)
 
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
 
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
 
static size_t Buffer_AppendIFSFileData (buffer_t *ifs_data, fsentry_t *fsentry); // writes the given filesystem entry's file data (i.e. its contents) to the IFS buffer
 
static int Buffer_StripELFFile (buffer_t *file, const char **saved_sections, const size_t saved_section_count, const char *indicative_pathname); // strips an ELF file buffer the way mkifs does it and returns whether it succeeded
 
static size_t add_fsentry (fsentry_t **fsentries, size_t *fsentry_count, parms_t *entry_parms, const char *stored_pathname, const char *buildhost_pathname); // stack up a new filesystem entry
 
static int fsentry_compare_pathnames_cb (const void *a, const void *b); // qsort() comparison callback that sorts filesystem entries by pathnames
 
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
 
 
 
 
 
// imported function prototypes
 
extern int dump_ifs_info (const char *ifs_pathname, bool want_everything); // [implemented in ifsdump.c] dumps detailed info about a particular IFS file on the standard output, returns 0 on success and >0 on error
 
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
 
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
 
 
 
 
 
int32_t update_checksum (const void *data, const size_t data_len, const bool is_foreign_endianness)
 
{
 
   // 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
 
 
 
   uint8_t accumulator[4] = { 0, 0, 0, 0 };
 
   const char *current_char_ptr;
 
   int32_t image_cksum;
 
   size_t i;
 
 
 
   image_cksum = 0;
 
   current_char_ptr = data;
 
   for (i = 0; i < data_len; i++)
 
   {
 
      accumulator[i % 4] = *current_char_ptr;
 
      if (i % 4 == 3)
 
         if (is_foreign_endianness)
 
            image_cksum += (accumulator[3] << 0) + (accumulator[2] << 8) + (accumulator[1] << 16) + (accumulator[0] << 24);
 
         else
 
            image_cksum += (accumulator[0] << 0) + (accumulator[1] << 8) + (accumulator[2] << 16) + (accumulator[3] << 24);
 
      current_char_ptr++;
 
   }
 
 
 
   return (is_foreign_endianness ? __builtin_bswap32 (-image_cksum) : -image_cksum);
 
}
 
 
 
 
 
static long long read_integer (const char *str)
 
{
 
   // 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)
 
 
 
   char *endptr = NULL;
 
   long long ret = strtoll (str, &endptr, 0); // use strtoll() to handle hexadecimal (0x...), octal (0...) and decimal (...) bases
 
   if (endptr != NULL)
 
   {
 
      if      ((*endptr == 'k') || (*endptr == 'K')) ret *= (size_t) 1024;
 
      else if ((*endptr == 'm') || (*endptr == 'M')) ret *= (size_t) 1024 * 1024;
 
      else if ((*endptr == 'g') || (*endptr == 'G')) ret *= (size_t) 1024 * 1024 * 1024;
 
      else if ((*endptr == 't') || (*endptr == 'T')) ret *= (size_t) 1024 * 1024 * 1024 * 1024; // future-proof enough, I suppose?
 
   }
 
   return (ret);
 
}
 
 
 
 
 
static char *resolve_pathname (const char *pathname, const char *search_paths_or_NULL_for_MKIFS_PATH_envvar)
 
{
 
   // locates pathname among search path and returns resolved pathname (static buffer) or NULL.
 
 
 
   typedef struct default_path_s { bool uses_processor_base; char *subpath; } default_path_t;
 
 
 
   static const default_path_t default_paths[] =
 
   {
 
      { false, "/sbin"     }, // prefix with $PROCESSOR/
 
      { false, "/usr/sbin" }, // prefix with $PROCESSOR/
 
      { false, "/boot/sys" }, // prefix with $PROCESSOR/
 
      { true,  "/boot/sys" }, // prefix with $PROCESSOR_BASE/
 
      { false, "/bin"      }, // prefix with $PROCESSOR/
 
      { false, "/usr/bin"  }, // prefix with $PROCESSOR/
 
      { false, "/lib"      }, // prefix with $PROCESSOR/
 
      { false, "/lib/dll"  }, // prefix with $PROCESSOR/
 
      { false, "/usr/lib"  }  // prefix with $PROCESSOR/
 
   };
 
   static thread_local char *resolved_pathname = NULL;
 
 
 
   size_t defaultpath_index;
 
   struct stat stat_buf;
 
   const char *nextsep;
 
   const char *token;
 
 
 
   // initial allocation (per thread)
 
   if (resolved_pathname == NULL)
 
   {
 
      resolved_pathname 
= malloc (MAXPATHLEN
); 
      ASSERT_WITH_ERRNO (resolved_pathname);
 
   }
 
 
 
   // is it an absolute pathname (POSIX and Windows variants) ?
 
   if (IS_DIRSEP (pathname[0])
 
#ifdef _WIN32
 
       || (isalpha (pathname
[0]) && (pathname
[1] == ':') && IS_DIRSEP 
(pathname
[2]))  
#endif // _WIN32
 
       )
 
      strcpy_s (resolved_pathname, MAXPATHLEN, pathname); // in this case, it MUST exist at its designated location (either absolute or relative to the current working directory)
 
   else // the path is relative, search it among the search paths we have
 
   {
 
      // QNX docs:
 
      // 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.
 
      // You can define multiple -r options; each adds a set of paths to
 
      // search for files. 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.
 
      // Normally, mkifs searches any paths defined in $MKIFS_PATH when
 
      // it was called and then the default paths within $QNX_TARGET. The
 
      // default paths are based on the CPU architecture specified by
 
      // $PROCESSOR and $PROCESSOR_BASE. If you specify -r options, mkifs
 
      // searches the default paths prefixed with each dir variable before
 
      // searching those within $QNX_TARGET. These paths are:
 
      //   dir/${PROCESSOR}/sbin
 
      //   dir/${PROCESSOR}/usr/sbin
 
      //   dir/${PROCESSOR}/boot/sys
 
      //   dir/${PROCESSOR_BASE}/boot/sys
 
      //   dir/${PROCESSOR}/bin
 
      //   dir/${PROCESSOR}/usr/bin
 
      //   dir/${PROCESSOR}/lib
 
      //   dir/${PROCESSOR}/lib/dll
 
      //   dir/${PROCESSOR}/usr/lib
 
      // 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. For example, if you
 
      // wanted to include /scratch/aarch64le/sbin/devb-sata, you would
 
      // specify a -r option like this:
 
      //   -r /scratch
 
      // Note that you don't include $PROCESSOR or $PROCESSOR_BASE in dir.
 
 
 
      //  - search all paths in explicit path/[default paths] (if explicit path supplied)
 
      //  - search all paths in (-r flags if have some|MKIFS_PATH)/[default paths] (if no explicit path supplied)
 
      //  - search all paths in $QNX_TARGET/[default paths]
 
 
 
      // 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
 
      if (search_paths_or_NULL_for_MKIFS_PATH_envvar == NULL)
 
         search_paths_or_NULL_for_MKIFS_PATH_envvar 
= (SEARCH_PATH 
!= NULL 
? SEARCH_PATH 
: getenv ("MKIFS_PATH")); 
 
 
      // construct a potential final path using each element of the search path
 
      if (search_paths_or_NULL_for_MKIFS_PATH_envvar != NULL)
 
      {
 
         token = (*search_paths_or_NULL_for_MKIFS_PATH_envvar != 0 ? search_paths_or_NULL_for_MKIFS_PATH_envvar : NULL);
 
         nextsep 
= (token 
!= NULL 
? &token
[strcspn (token
, PATH_SEP
)] : NULL
); 
         while (token != NULL)
 
         {
 
            for (defaultpath_index = 0; defaultpath_index < sizeof (default_paths) / sizeof (default_paths[0]); defaultpath_index++)
 
            {
 
               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);
 
               if ((stat (resolved_pathname, &stat_buf) == 0) && S_ISREG (stat_buf.st_mode))
 
                  return (resolved_pathname); // if a file can indeed be found at this location, stop searching
 
            }
 
 
 
            token = (*nextsep != 0 ? nextsep + 1 : NULL);
 
            nextsep 
= (token 
!= NULL 
? &token
[strcspn (token
, PATH_SEP
)] : NULL
); 
         }
 
      }
 
 
 
      // file not found in search paths: look under QNX_TARGET
 
      for (defaultpath_index = 0; defaultpath_index < sizeof (default_paths) / sizeof (default_paths[0]); defaultpath_index++)
 
      {
 
         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);
 
         if ((stat (resolved_pathname, &stat_buf) == 0) && S_ISREG (stat_buf.st_mode))
 
            return (resolved_pathname); // if a file can indeed be found at this location, stop searching
 
      }
 
   }
 
 
 
   errno = ENOENT; // we exhausted all possibilities
 
   return (NULL); // file not found, return with ENOENT
 
}
 
 
 
 
 
static size_t Buffer_WriteIFSDirectoryEntryAt (buffer_t *ifs, const size_t write_offset, const fsentry_t *fsentry)
 
{
 
   // writes a directory entry in the image filesystem buffer pointed to by ifs at write_offset (or fakes so if ifs is NULL)
 
   // and return the number of bytes written (or that would have been written)
 
 
 
   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";
 
 
 
   size_t datalen;
 
   size_t count;
 
 
 
   count = 0;
 
   if (ifs != NULL)
 
      ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, &fsentry->header, sizeof (fsentry->header))); // write the entry header (PACKED STRUCT)
 
   count += sizeof (fsentry->header);
 
   if (S_ISREG (fsentry->header.mode))
 
   {
 
      if (ifs != NULL)
 
         ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, &fsentry->u.file.offset, sizeof (uint32_t))); // write offset
 
      count += sizeof (uint32_t);
 
      if (ifs != NULL)
 
         ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, &fsentry->u.file.size,   sizeof (uint32_t))); // write size
 
      count += sizeof (uint32_t);
 
      datalen 
= strlen (fsentry
->u.
file.
path) + 1; 
      if (ifs != NULL)
 
         ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, fsentry->u.file.path, datalen)); // write null-terminated path (no leading slash)
 
      count += datalen;
 
   }
 
   else if (S_ISDIR (fsentry->header.mode))
 
   {
 
      datalen 
= strlen (fsentry
->u.
dir.
path) + 1; 
      if (ifs != NULL)
 
         ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, fsentry->u.dir.path, datalen)); // write null-terminated path (no leading slash)
 
      count += datalen;
 
   }
 
   else if (S_ISLNK (fsentry->header.mode))
 
   {
 
      if (ifs != NULL)
 
         ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, &fsentry->u.symlink.sym_offset, sizeof (uint16_t))); // write offset
 
      count += sizeof (uint16_t);
 
      if (ifs != NULL)
 
         ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, &fsentry->u.symlink.sym_size,   sizeof (uint16_t))); // write size
 
      count += sizeof (uint16_t);
 
      datalen 
= strlen (fsentry
->u.
symlink.
path) + 1; 
      if (ifs != NULL)
 
         ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, fsentry->u.symlink.path, datalen)); // write null-terminated path (no leading slash)
 
      count += datalen;
 
      datalen 
= strlen (fsentry
->u.
symlink.
contents) + 1; 
      if (ifs != NULL)
 
         ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, fsentry->u.symlink.contents, datalen)); // write null-terminated symlink contents
 
      count += datalen;
 
   }
 
   else
 
   {
 
      if (ifs != NULL)
 
         ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, &fsentry->u.device.dev,  sizeof (uint32_t))); // write dev number
 
      count += sizeof (uint32_t);
 
      if (ifs != NULL)
 
         ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, &fsentry->u.device.rdev, sizeof (uint32_t))); // write rdev number
 
      count += sizeof (uint32_t);
 
      datalen 
= strlen (fsentry
->u.
device.
path) + 1; 
      if (ifs != NULL)
 
         ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, fsentry->u.device.path, datalen)); // write null-terminated path (no leading slash)
 
      count += datalen;
 
   }
 
 
 
   ASSERT (count <= fsentry->header.size, "attempt to write invalid dirent (claimed size %zd, written size %zd). Aborting.", (size_t) fsentry->header.size, count);
 
   if (count < fsentry->header.size)
 
   {
 
      if (ifs != NULL)
 
         ASSERT_WITH_ERRNO (Buffer_WriteAt (ifs, write_offset + count, zeropad_buffer, fsentry->header.size - count)); // pad as necessary
 
      count += fsentry->header.size - count;
 
   }
 
 
 
   return (count);
 
}
 
 
 
 
 
static size_t Buffer_AppendIFSFileData (buffer_t *ifs_data, fsentry_t *fsentry)
 
{
 
   // writes the given filesystem entry's file data (i.e. its contents) to the IFS buffer
 
 
 
   elf_program_header_t *phdr;
 
   elf_header_t *elf;
 
   size_t fixed_physical_addr;
 
   size_t corrective_offset;
 
   //size_t segment_type;
 
   size_t segment_size;
 
   size_t table_index;
 
   size_t table_count;
 
   size_t data_offset;
 
 
 
   ASSERT (S_ISREG (fsentry->header.mode), "function called for invalid dirent"); // consistency check
 
   data_offset = ifs_data->size; // see where we are
 
 
 
   // is the file we're storing a preprocessed ELF file ?
 
   if ((fsentry->header.ino & IFS_INO_PROCESSED_ELF)
 
#ifndef PROCNTO_WIP
 
      && (strstr (fsentry
->u.
file.
path, "/procnto-smp-instr") == NULL
)  
#endif // !PROCNTO_WIP
 
       )
 
   {
 
 
 
      elf = (elf_header_t *) fsentry->u.file.UNSAVED_databuf; // quick access to ELF header
 
      table_count = ELF_GET_NUMERIC (elf, elf, program_header_table_len); // get the number of program headers
 
      for (table_index = 0; table_index < table_count; table_index++)
 
      {
 
         phdr = (elf_program_header_t *) &fsentry->u.file.UNSAVED_databuf[ELF_GET_NUMERIC (elf, elf, program_header_table_offset) + (size_t) ELF_GET_NUMERIC (elf, elf, program_header_item_size) * table_index]; // quick access to program header
 
         //segment_type = ELF_GET_NUMERIC (elf, phdr, segment_type); // get segment type
 
         //if (!((segment_type >= 2) && (segment_type <= 7) || ((segment_type >= 0x6474e550) && (segment_type <= 0x6474e552)) || (segment_type == 0x70000001)))
 
         //   continue; // NOTE: only certain segments types must be corrected
 
 
 
 
 
         corrective_offset = ELF_GET_NUMERIC (elf, phdr, virtual_addr) - ELF_GET_NUMERIC (elf, phdr, file_offset);
 
         segment_size = ELF_GET_NUMERIC (elf, phdr, size_in_memory); // get this ELF segment's occupied size in memory
 
         if (segment_size != 0) // only patch the physical address of segments that have an actual size in memory
 
         {
 
            fixed_physical_addr = ELF_GET_NUMERIC (elf, phdr, physical_addr) + image_base + data_offset - corrective_offset;
 
            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)
 
         }
 
      }
 
   }
 
 
 
   ASSERT_WITH_ERRNO (Buffer_Append (ifs_data, fsentry->u.file.UNSAVED_databuf, fsentry->u.file.size)); // write file data blob
 
   return (ifs_data->size - data_offset); // return the number of bytes written
 
}
 
 
 
 
 
static inline size_t Buffer_LocateOrAppendIfNecessaryAndReturnOffsetOf (buffer_t *buffer, const char *str)
 
{
 
   // helper function used in add_fsentry(): locates or appends str to buffer and returns its relative offset in the buffer
 
 
 
   size_t str_len_including_terminator 
= strlen (str
) + 1;  
   void *occurrence = Buffer_FindFirst (buffer, str, str_len_including_terminator);
 
   if (occurrence == NULL)
 
   {
 
      ASSERT_WITH_ERRNO (Buffer_Append (buffer, str, str_len_including_terminator));
 
      occurrence = Buffer_FindFirst (buffer, str, str_len_including_terminator);
 
      ASSERT_WITH_ERRNO (occurrence);
 
   }
 
   return (Buffer_OffsetOf (buffer, occurrence)); // can't fail
 
}
 
 
 
 
 
static int Buffer_StripELFFile (buffer_t *file, const char **saved_sections, const size_t saved_section_count, const char *indicative_pathname)
 
{
 
   // NOTE: for each ELF file, mkifs
 
   // -> 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)
 
   // -> 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
 
   // FIXME: what if a thrown away section is located between two program segments ? are they collapsed, moving the segments beyond it one slot down ?
 
 
 
   // reconstructed ELF:
 
   // ==== START OF FILE ====
 
   // ELF header
 
   // program header table
 
   //  (same sections, just p_addr offset changed)
 
   // section data 5 (named ".note.gnu.build-id")
 
   //  "............GNU....ZY.....c.o..l"
 
   // PROGRAM
 
   // sections table
 
   // + section 1: ALL ZEROES
 
   // + 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"
 
   // + 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"
 
   // + 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.."
 
   // + section 5: fileoffs 0x190 size 0x32 --> ".note.gnu.build-id" --> GNU build ID
 
   // + section 6: fileoffs 0x256e size 0x40 --> ".shstrtab" --> sections names strings table
 
   // section data 2 (named "QNX_info")
 
   //  (QNX binary description)
 
   // section data 3 (named ".gnu_debuglink")
 
   //  (debug file)
 
   // section data 4 (named "QNX_usage")
 
   //  (help text)
 
   // section data 6 (named ".shstrtab")
 
   //  "\0"
 
   //  ".shstrtab\0"
 
   //  "QNX_info\0"
 
   //  ".gnu_debuglink\0"
 
   //  "QNX_usage\0"
 
   //  ".note.gnu.build-id\0"
 
   // ==== END OF FILE ====
 
 
 
   #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
 
   #define ADD_SECTION(section_name,section_ptr) do { \
 
      void *reallocated_ptr = realloc (elf_sections, (elf_section_count + 1) * sizeof (elf_section_t)); \
 
      ASSERT_WITH_ERRNO (reallocated_ptr); \
 
      elf_sections = reallocated_ptr; \
 
      elf_sections[elf_section_count].name = (section_name); \
 
      Buffer_Initialize (&elf_sections[elf_section_count].data); \
 
      *(section_ptr) = &elf_sections[elf_section_count]; \
 
      elf_section_count++; \
 
   } while (0)
 
 
 
   typedef struct elf_section_s
 
   {
 
      const char *name;
 
      elf_section_header_t header;
 
      buffer_t data;
 
   } elf_section_t;
 
 
 
   const elf_program_header_t *phdr;
 
   const elf_section_header_t *shdr;
 
   elf_section_t *elf_sections = NULL; // mallocated
 
   elf_section_t *elf_section = NULL;
 
   size_t elf_section_count = 0;
 
   size_t new_shdrtable_offset;
 
   size_t new_shdrtable_len;
 
   size_t sectiondata_start;
 
   size_t sectiondata_size;
 
   size_t array_index;
 
   size_t table_index;
 
   size_t table_count;
 
   size_t page_size;
 
 
 
   // find out the platform page size
 
   if (ELF_GET_NUMERIC (ELFHDR, ELFHDR, instruction_set) == ELF_MACHINE_X86_64)
 
      page_size = 4 * 1024; // 4 kb pages on Intel processors
 
   else if (ELF_GET_NUMERIC (ELFHDR, ELFHDR, instruction_set) == ELF_MACHINE_AARCH64)
 
      page_size = 16 * 1024; // 16 kb pages on ARM64
 
   else
 
   {
 
      errno = ENOTSUP; // unsupported architecture: set errno to something meaningful
 
      return (0); // and return an error value
 
   }
 
 
 
   // parse the program header table, and measure the farthest offset known by this table where we'll write the reconstructed section headers table
 
 
 
   new_shdrtable_offset = 0;
 
   table_count = ELF_GET_NUMERIC (ELFHDR, ELFHDR, program_header_table_len);
 
   for (table_index = 0; table_index < table_count; table_index++)
 
   {
 
      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
 
      if (ELF_GET_NUMERIC (ELFHDR, phdr, file_offset) + ELF_GET_NUMERIC (ELFHDR, phdr, size_in_file) > new_shdrtable_offset)
 
         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
 
   }
 
   /*
 
   size_t new_shdrtable_offset_method2 = 0;
 
   for (table_index = 0; table_index < table_count; table_index++)
 
   {
 
      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
 
      size_t segment_type = ELF_GET_NUMERIC (ELFHDR, phdr, segment_type); // get segment type
 
      if (!((segment_type >= 2) && (segment_type <= 7)))
 
         continue; // NOTE: only certain segments types must be corrected
 
      if (ELF_GET_NUMERIC (ELFHDR, phdr, file_offset) + ELF_GET_NUMERIC (ELFHDR, phdr, size_in_memory) > new_shdrtable_offset_method2)
 
         new_shdrtable_offset_method2 = ELF_GET_NUMERIC (ELFHDR, phdr, file_offset) + ELF_GET_NUMERIC (ELFHDR, phdr, size_in_memory);
 
   }
 
   if (new_shdrtable_offset_method2 > new_shdrtable_offset)
 
      LOG_DEBUG ("METHOD2: %llx > %llx", new_shdrtable_offset_method2, new_shdrtable_offset);*/
 
   //new_shdrtable_offset = ROUND_TO_UPPER_MULTIPLE (new_shdrtable_offset, page_size); // round to page size
 
 
 
   // re-create the section header table
 
   ADD_SECTION (".shstrtab", &elf_section); // the first section will be the section names strings table
 
   ASSERT_WITH_ERRNO (Buffer_InitWithByteArray (&elf_section->data, "\0")); // initialize an empty section headers strings table
 
   ASSERT_WITH_ERRNO (Buffer_AppendByteArray (&elf_section->data, ".shstrtab\0")); // append ".shstrtab" *INCLUDING* its null terminator
 
 
 
   // go through the saved sections array and see if such an ELF section is present in the ELF file
 
   for (array_index = 0; array_index < saved_section_count; array_index++)
 
      if ((shdr = elf_get_section_header_by_name (ELFHDR, saved_sections[array_index])) != NULL) // does this ELF have such a section ?
 
      {
 
         ADD_SECTION (saved_sections[array_index], &elf_section); // yes, so save it
 
         sectiondata_start = ELF_GET_NUMERIC (ELFHDR, shdr, file_offset); // identify section data start offset
 
         sectiondata_size = ELF_GET_NUMERIC (ELFHDR, shdr, size); // identify section data length
 
         if (sectiondata_start + sectiondata_size >= new_shdrtable_offset) // should this section be moved ?
 
            ASSERT_WITH_ERRNO (Buffer_InitWithData (&elf_section->data, &file->bytes[sectiondata_start], sectiondata_size)); // have a copy of this section's data
 
         else
 
            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
 
         //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);
 
 
 
         // prepare this section's "fixed" header
 
         memcpy (&elf_section
->header
, shdr
, ELF_STRUCT_SIZE 
(ELFHDR
, shdr
)); // have a copy of the old section header first  
         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
 
      }
 
 
 
   // jump over the new section headers table and write the saved sections data after the section headers table
 
   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
 
   for (table_index = 1; table_index < elf_section_count; table_index++)
 
   {
 
      elf_section = &elf_sections[table_index]; // quick access to ELF section about to be written
 
      if (elf_section->data.bytes != NULL) // was this section data backed up waiting to be relocated ?
 
      {
 
         ELF_SET_NUMERIC (ELFHDR, &elf_section->header, file_offset, file->size); // fix section offset
 
         Buffer_AppendBuffer (file, &elf_section->data); // append this section's data to the ELF file
 
      }
 
   }
 
   // write the section header strings table as the last section
 
   elf_section = &elf_sections[0]; // quick access to ELF section about to be written
 
   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
 
   ELF_SET_NUMERIC (ELFHDR, &elf_section->header, type, ELF_SECTIONTYPE_STRINGTABLE); // section type (SHT_STRTAB)
 
   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)
 
   ELF_SET_NUMERIC (ELFHDR, &elf_section->header, virtual_addr, 0); // this section does not need to be mapped
 
   ELF_SET_NUMERIC (ELFHDR, &elf_section->header, file_offset, file->size); // fix section offset
 
   ELF_SET_NUMERIC (ELFHDR, &elf_section->header, size, elf_sections[0].data.size); // section size
 
   ELF_SET_NUMERIC (ELFHDR, &elf_section->header, linked_index, 0); // this section is not linked to any other
 
   ELF_SET_NUMERIC (ELFHDR, &elf_section->header, info, 0); // this section has no additional info
 
   ELF_SET_NUMERIC (ELFHDR, &elf_section->header, alignment, 1); // this section is byte-aligned
 
   ELF_SET_NUMERIC (ELFHDR, &elf_section->header, entry_size, 0); // this section is not a table, so entry_size is zero
 
   Buffer_AppendBuffer (file, &elf_section->data); // append section headers strings table section data to ELF file
 
 
 
   // now write the section headers table
 
   memset (&file
->bytes
[new_shdrtable_offset
], 0, ELF_STRUCT_SIZE 
(ELFHDR
, &elf_sections
[0].
header)); // the first section header is always zerofilled  
   for (table_index = 1; table_index < elf_section_count; table_index++)
 
      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
 
   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
 
 
 
   // and finally fix the ELF master header
 
   new_shdrtable_len = 1 + elf_section_count; // take in account that the first entry in the section headers table is empty
 
   ELF_SET_NUMERIC (ELFHDR, ELFHDR, section_header_table_offset, new_shdrtable_offset);
 
   ELF_SET_NUMERIC (ELFHDR, ELFHDR, section_header_table_len, new_shdrtable_len);
 
   ELF_SET_NUMERIC (ELFHDR, ELFHDR, section_header_names_idx, elf_section_count); // the section headers strings table is the last section
 
 
 
   // align size with page size (4096 on x86, 16k on ARM), zerofilling the extra space
 
   ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (file, ROUND_TO_UPPER_MULTIPLE (file->size, page_size)));
 
 
 
   // cleanup
 
   for (table_index = 0; table_index < elf_section_count; table_index++)
 
      Buffer_Forget (&elf_sections[table_index].data); // free all sections' backing buffers
 
 
 
   #undef ELFHDR // undefine the macro that used to always point to the ELF header at the beginning of the file
 
   return (1); // success
 
}
 
 
 
 
 
static size_t add_fsentry (fsentry_t **fsentries, size_t *fsentry_count, parms_t *entry_parms, const char *stored_pathname, const char *buildhost_pathname)
 
{
 
   static thread_local char *candidate_pathname = NULL;
 
   static int inode_count = 0; // will be preincremented each time this function is called
 
 
 
   const char *original_stored_pathname = NULL;
 
   buffer_t *shstrtab = NULL;
 
   const char *canonical_dylib_name;
 
   const char *dynamic_strings; // strings table of the ".dynamic" section
 
   const char *last_dirsep;
 
   char *global_envstring = NULL;
 
   size_t global_envstring_len = 0;
 
   char *startup_name = NULL;
 
   char *procnto_name = NULL;
 
   char *resolved_pathname;
 
   void *reallocated_ptr;
 
   void *old_data;
 
   struct stat stat_buf;
 
   fsentry_t *fsentry;
 
 
 
   // initial allocation (per thread)
 
   if (candidate_pathname == NULL)
 
   {
 
      candidate_pathname 
= malloc (MAXPATHLEN
); 
      ASSERT_WITH_ERRNO (candidate_pathname);
 
   }
 
 
 
   if (S_ISDIR (entry_parms->st_mode)) // are we storing a directory ?
 
   {
 
      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);
 
   }
 
   else if (S_ISREG (entry_parms->st_mode)) // else are we storing a regular file ?
 
   {
 
      if (strcmp (stored_pathname
, "/proc/boot/boot") == 0) // is it the kernel ?  
      {
 
         // HACK: for now just consider the kernel as a binary blob
 
         // FIXME: reimplement properly
 
#ifdef PROCNTO_WIP // FIXME: segment corruption somewhere!
 
         char *linebit_start;
 
         char *content_line;
 
         char *write_ptr;
 
         char *token;
 
         char *value;
 
         char *ctx;
 
         bool is_quoted_context;
 
         bool was_string_split;
 
 
 
         // parse each line of contents
 
         ASSERT (entry_parms->data.len > 0, "kernel specification without inline contents");
 
         for (content_line = strtok_r (entry_parms->data.bytes, "\n", &ctx); content_line != NULL; content_line = strtok_r (NULL, "\n", ctx))
 
         {
 
               content_line++; // skip leading spaces
 
            if ((*content_line == '#') || (*content_line == 0))
 
               continue; // skip comments and empty lines
 
 
 
            // format of a line: [attributes] [env assignation] [...] [executable] [arg] [...] [comment]
 
            // example: "[uid=0 gid=0 perms=0700] CONFIG_PATH=/proc/boot:/etc procnto-smp-instr -v -mr -d 0777 -u 0777"
 
            //LOG_DEBUG ("parsing line: %s", content_line);
 
 
 
            // does this line start with an attribute block ?
 
            if (*content_line == '[')
 
            {
 
               content_line++; // skip the leading square bracket
 
               linebit_start = content_line; // remember where it starts
 
               is_quoted_context = false; // reach the next unescaped closing square bracket that is not between quotes
 
               while ((*content_line != 0) && !((*content_line == ']') && (content_line[-1] != '\\') && !is_quoted_context))
 
               {
 
                  if (*content_line == '"')
 
                     is_quoted_context ^= true; // remember when we're between quotes
 
                  else if (!is_quoted_context && (*content_line == ' '))
 
                     *content_line = RECORD_SEP[0]; // turn all spaces outside quoted contexts into an ASCII record separator to ease token splitting
 
                  content_line++; // reach the next unescaped closing square bracket
 
               }
 
               if (*content_line != ']')
 
               {
 
                  LOG ("warning", 0, "syntax error in \"%s\" line %d: unterminated attributes block (skipping)", buildfile_pathname, lineno);
 
                  continue; // invalid attribute block, skip line
 
               }
 
               *content_line = 0; // end the attribute block so that it is a parsable C string
 
 
 
               // now parse the attribute tokens (NOTE: THE LIST OF ALLOWED ATTRIBUTES HERE IS NOT DOCUMENTED)
 
               token = strtok_r (linebit_start, RECORD_SEP, &ctx);
 
               while (token != NULL)
 
               {
 
                  #define REACH_TOKEN_VALUE() do { value = strchr (token, '=') + 1; if (*value == '"') value++; } while (0)
 
                  if (false)
 
                  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.)  
                  else if (strncmp (token
, "uid=",     4) == 0) { REACH_TOKEN_VALUE 
(); entry_parms
->uid     
= (int) read_integer 
(value
); }  
                  else if (strncmp (token
, "gid=",     4) == 0) { REACH_TOKEN_VALUE 
(); entry_parms
->gid     
= (int) read_integer 
(value
); }  
                  else if (strncmp (token
, "perms=",   6) == 0) { REACH_TOKEN_VALUE 
(); entry_parms
->perms   
= (int) read_integer 
(value
); }  
                  else if (strcmp (token
, "+followlink") == 0) entry_parms
->should_follow_symlinks 
= true;  
                  else if (strcmp (token
, "-followlink") == 0) entry_parms
->should_follow_symlinks 
= false;  
                  else if (strcmp (token
, "+keeplinked") == 0) entry_parms
->should_keep_ld_output 
= true;  
                  else if (strcmp (token
, "-keeplinked") == 0) entry_parms
->should_keep_ld_output 
= false;  
                  else LOG_WARNING ("unimplemented bootstrap executable attribute in \"%s\" line %d: '%s'", buildfile_pathname, lineno, token);
 
                  #undef REACH_TOKEN_VALUE
 
                  token = strtok_r (NULL, RECORD_SEP, &ctx); // proceed to next attribute token
 
               }
 
 
 
               content_line++; // reach the next character
 
               while ((*content_line 
!= 0) && isspace (*content_line
))  
                  content_line++; // skip leading spaces
 
            } // end of "this line starts with an attributes block"
 
 
 
            // there's data in this line. We expect an executable OR a variable name. Read it and unescape escaped characters
 
            while (*content_line != 0)
 
            {
 
               linebit_start = content_line; // remember the name starts here
 
               write_ptr = linebit_start;
 
               is_quoted_context = (*content_line == '"');
 
               if (is_quoted_context)
 
                  content_line++; // skip a possible initial quote in the name
 
               while ((*content_line 
!= 0) && ((!is_quoted_context 
&& (*content_line 
!= '=') && !isspace (*content_line
)) || (is_quoted_context 
&& (*content_line 
== '"'))))  
               {
 
                  if (*content_line == '\\')
 
                  {
 
                     content_line++;
 
                     *write_ptr++ = *content_line; // unescape characters that are escaped with '\'
 
                  }
 
                  else
 
                     *write_ptr++ = *content_line;
 
                  content_line++;
 
               }
 
 
 
               // we reached a closing quote, a space OR an equal sign
 
               if (*content_line == '=')
 
               {
 
                  // it's an environment variable assignation
 
                  *write_ptr++ = *content_line++; // skip the equal sign
 
                  is_quoted_context = (*content_line == '"');
 
                  if (is_quoted_context)
 
                     content_line++; // skip a possible initial quote in the value
 
                  while ((*content_line 
!= 0) && ((!is_quoted_context 
&& (*content_line 
!= '=') && !isspace (*content_line
)) || (is_quoted_context 
&& (*content_line 
== '"'))))  
                  {
 
                     if (*content_line == '\\')
 
                     {
 
                        content_line++;
 
                        *write_ptr++ = *content_line; // unescape characters that are escaped with '\'
 
                     }
 
                     else
 
                        *write_ptr++ = *content_line;
 
                     content_line++;
 
                  }
 
                  if (*write_ptr != 0)
 
                  {
 
                     *write_ptr = 0; // terminate the string if necessary
 
                     was_string_split = true;
 
                  }
 
                  else
 
                     was_string_split = false;
 
                  if (is_quoted_context && (*content_line == '"'))
 
                     content_line++; // skip a possible final quote
 
                  while ((*content_line 
!= 0) && isspace (*content_line
))  
                     content_line++; // skip spaces
 
 
 
                  // now linebit_start is of the form "NAME=VALUE"
 
                  LOG_DEBUG ("assignation: [%s]", linebit_start);
 
 
 
                  // TODO: grow global_envstring
 
 
 
                  //reallocated_ptr = realloc (global_envstring, global_envstring_len + strlen ())
 
 
 
                  if (was_string_split)
 
                     *write_ptr = ' '; // restore string continuity for parsing to continue
 
                  while ((*content_line 
!= 0) && isspace (*content_line
))  
                     content_line++; // skip spaces
 
               }
 
               else // it's either a closing quote or a space
 
               {
 
                  *write_ptr = 0; // terminate the string
 
                  if (is_quoted_context && (*content_line == '"'))
 
                     content_line++; // skip a possible final quote
 
 
 
                  LOG_DEBUG ("exe name: [%s]", linebit_start);
 
 
 
                  while ((*content_line 
!= 0) && isspace (*content_line
))  
                     content_line++; // skip leading spaces
 
 
 
                  // it's an executable name. As per QNX docs, the first executable must be startup-*, the last executable must be procnto.
 
                  if (startup_name == NULL)
 
                     startup_name = strdup (linebit_start);
 
                  else
 
                  {
 
                     if (procnto_name != NULL)
 
                     procnto_name = strdup (linebit_start);
 
                  }
 
 
 
                  if ((*content_line == '#') || (*content_line == 0))
 
                     break; // if we reach the end of the line, stop parsing
 
 
 
                  // what comes after now must be optional arguments
 
                  while ((*content_line 
!= 0) && isspace (*content_line
))  
                     content_line++; // skip leading spaces
 
 
 
                  // FIXME: parse executable command-line arguments
 
 
 
                  break; // stop parsing once all the arguments have been read
 
               }
 
            }
 
         } // end of parsing
 
         free (entry_parms
->data.
bytes); // free the inline specification once it's parsed  
         entry_parms->data.bytes = NULL;
 
         entry_parms->data.len = 0;
 
 
 
         ASSERT (startup_name && *startup_name, "the QNX startup executable (startup-*) is missing in this bootstrap inline specification");
 
         ASSERT (procnto_name && *procnto_name, "the QNX kernel (procnto-*) is missing in this bootstrap inline specification");
 
 
 
         // now we know which startup and procnto executables to use
 
         LOG_DEBUG ("Startup: %s", startup_name);
 
         LOG_DEBUG ("Kernel: %s", procnto_name);
 
 
 
         sprintf (candidate_pathname
, "%s/%s", (entry_parms
->prefix 
!= NULL 
? entry_parms
->prefix 
: ""), procnto_name
); // fix the entry name  
         stored_pathname = candidate_pathname;
 
         entry_parms->extra_ino_flags |= /*IFS_INO_PROCESSED_ELF | */IFS_INO_BOOTSTRAP_EXE; // procnto needs to have these flags stamped on the inode
 
         entry_parms->st_mode = S_IFREG | entry_parms->perms; // apply specified procnto permissions
 
         image_kernel_ino = entry_parms->extra_ino_flags | (inode_count + 1);
 
 
 
         static thread_local char linker_pathname[MAXPATHLEN] = "";
 
         static thread_local char linker_sysroot_arg[MAXPATHLEN] = "";
 
         static thread_local char linker_script_pathname_arg[MAXPATHLEN] = "";
 
         static thread_local char procnto_buildhost_pathname[MAXPATHLEN] = "";
 
         static thread_local char procnto_sym_filename[MAXPATHLEN] = "";
 
 
 
         // construct the arguments that are based on environment variables (infer QNX_HOST from QNX_TARGET)
 
#if defined(_WIN32)
 
         sprintf (linker_pathname
, "%s/../../host/win64/x86_64/usr/bin/%s-ld.exe", QNX_TARGET
, (strcmp (image_processor
, "x86_64") == 0 ? "x86_64-pc-nto-qnx8.0.0" : "aarch64-unknown-nto-qnx8.0.0")); // Win32: note the .exe extension  
#elif defined(__linux__)
 
         sprintf (linker_pathname
, "%s/../../host/linux/x86_64/usr/bin/%s-ld", QNX_TARGET
, (strcmp (image_processor
, "x86_64") == 0 ? "x86_64-pc-nto-qnx8.0.0" : "aarch64-unknown-nto-qnx8.0.0"));  
#elif defined(__QNXNTO__)
 
         sprintf (linker_pathname
, "%s/../../host/qnx8/x86_64/usr/bin/%s-ld", QNX_TARGET
, (strcmp (image_processor
, "x86_64") == 0 ? "x86_64-pc-nto-qnx8.0.0" : "aarch64-unknown-nto-qnx8.0.0"));  
#else // wtf are you building this on?
 
#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.
 
#endif
 
         ASSERT (access (linker_pathname, 0) == 0, "host cross-linker for QNX8 \"%s\" not found", linker_pathname);
 
         sprintf (linker_sysroot_arg
, "--sysroot=%s/%s/", QNX_TARGET
, image_processor
);  
         sprintf (linker_script_pathname_arg
, "-T%s/%s/lib/nto.link", QNX_TARGET
, image_processor
);  
 
 
         resolved_pathname = resolve_pathname (procnto_name, entry_parms->search); // locate the procnto kernel location
 
         ASSERT (resolved_pathname, "QNX kernel \"%s\" not found in search path", procnto_name);
 
         strcpy (procnto_buildhost_pathname
, resolved_pathname
);  
 
 
         sprintf (procnto_sym_filename
, "%s.sym", procnto_name
);  
 
 
         const char *linker_argv[] = // construct the linker invokation argv
 
         {
 
            strrchr (linker_pathname
, '/') + 1, // "${TARGET_TRIPLE}-ld"  
            linker_sysroot_arg, // "--sysroot=${QNX_TARGET}/${TARGET_CPU}/"
 
            linker_script_pathname_arg, // "-T${QNX_TARGET}/${TARGET_CPU}/lib/nto.link"
 
            "--section-start",
 
            ".text=0xffff800000001000",
 
            "--no-relax",
 
            procnto_buildhost_pathname, // "${QNX_TARGET}/${TARGET_CPU}/boot/sys/procnto-smp-instr"
 
            "-o",
 
            procnto_sym_filename, // "procnto-smp-instr.sym"
 
            NULL
 
         };
 
         if (verbose_level > 2)
 
         {
 
            fprintf (stderr
, "ifstool: calling:");  
            for (table_index = 0; table_index < sizeof (linker_argv) / sizeof (linker_argv[0]) - 1; table_index++)
 
               fprintf (stderr
, " '%s'", linker_argv
[table_index
]);  
         }
 
         _spawnv (_P_WAIT, linker_pathname, linker_argv); // spawn the linker and produce a stripped procnto (wait for completion)
 
         if (!Buffer_ReadFromFile (&entry_parms->data, procnto_sym_filename)) // load the output file
 
            DIE_WITH_EXITCODE 
(1, "the host cross-linker failed to produce a readable stripped \"%s\" kernel: %s", procnto_sym_filename
, strerror (errno
)); 
         if (!entry_parms->should_keep_ld_output)
 
            unlink (procnto_sym_filename); // remove the linker output file if we want to
 
 
 
         // now strip this prelinked ELF kernel file
 
         Buffer_StripELFFile (&entry_parms->data, saved_ELF_sections, 1, stored_pathname); // strip the ELF file as per QNX docs (only keep ONE section, which is "QNX_info")
 
         entry_parms->extra_ino_flags |= IFS_INO_PROCESSED_ELF; // mark this inode as a preprocessed ELF file
 
 
 
#else // !PROCNTO_WIP
 
         /* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK */
 
         /* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK */
 
         /* HACK */
 
         /* HACK */ sprintf_s (candidate_pathname, MAXPATHLEN, "%s/procnto-smp-instr", (entry_parms->prefix != NULL ? entry_parms->prefix : "")); // HACK: fix the entry name
 
         /* HACK */ stored_pathname = candidate_pathname;
 
         /* HACK */ entry_parms->extra_ino_flags |= IFS_INO_PROCESSED_ELF | IFS_INO_BOOTSTRAP_EXE; // procnto needs to have these flags stamped on the inode
 
         /* HACK */ entry_parms->st_mode = S_IFREG | 0700; // procnto requires 0700 permissions
 
         /* HACK */ image_kernel_ino = entry_parms->extra_ino_flags | (inode_count + 1);
 
         /* HACK */ free (entry_parms
->data.
bytes); // discard inline contents  
         /* HACK */ Buffer_Initialize (&entry_parms->data);
 
         /* HACK */ if (!Buffer_ReadFromFile (&entry_parms->data, kernelfile_pathname)) // read kernel file as a precompiled binary blob
 
         /* HACK */ {
 
         /* HACK */    fprintf (stderr
, "fatal error: unable to read precompiled kernel file \"%s\" specified in --kernelfile argument\n", kernelfile_pathname
);  
         /* HACK */ }
 
         /* HACK */
 
         /* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK */
 
         /* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK */
 
#endif // PROCNTO_WIP
 
      }
 
      else if (entry_parms->is_compiled_bootscript) // else is it a startup script ?
 
         image_bootscript_ino = inode_count + 1; // save boot script inode number for image header
 
 
 
      // do we already know the data for this data blob ?
 
      if (entry_parms->data.bytes != NULL)
 
      {
 
         entry_parms->mtime = entry_parms->mtime_for_inline_files; // if so, set it a mtime equal to the mtime to use for inline files
 
         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);
 
      }
 
      else if (buildhost_pathname != NULL) // else was a source file pathname supplied ?
 
      {
 
         resolved_pathname = resolve_pathname (buildhost_pathname, entry_parms->search); // locate the file
 
         if (resolved_pathname == NULL)
 
            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
)); 
         if (!Buffer_ReadFromFile (&entry_parms->data, resolved_pathname))
 
            DIE_WITH_EXITCODE 
(1, "filesystem entry \"%s\" specified in \"%s\" line %d can't be read: %s", buildhost_pathname
, buildfile_pathname
, lineno
, strerror (errno
)); 
         stat (resolved_pathname, &stat_buf); // can't fail, since we could read it
 
         if (entry_parms->mtime == UINT32_MAX)
 
            entry_parms->mtime = (uint32_t) stat_buf.st_mtime;
 
         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);
 
      }
 
 
 
      // is the file we're storing an ELF file ?
 
      #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
 
      if ((entry_parms->data.size > 52) // file is big enough to contain an ELF header
 
          && (memcmp (ELF_GET_STRING 
(ELFHDR
, ELFHDR
, magic
), ELF_MAGIC_STR
, 4) == 0)) // file starts with the ELF magic  
      {
 
         // is the file we're storing a relocatable executable (i.e. a dynamic library) and should we check for its canonical name ?
 
         if ((ELF_GET_NUMERIC (ELFHDR, ELFHDR, type) == ELF_TYPE_DYNAMICLIB) && entry_parms->should_autosymlink_dylib)
 
         {
 
            // locate the sections we need (the dynamic section and its strings table)
 
            const elf_section_header_t *shdr_dynamic = elf_get_section_header_by_name (ELFHDR, ".dynamic");
 
            const elf_section_header_t *shdr_dynstr = elf_get_section_header_by_name (ELFHDR, ".dynstr");
 
 
 
            // make sure we have both the dynamic section header and its own strings table header
 
            if ((shdr_dynamic != NULL) && (shdr_dynstr != NULL))
 
            {
 
               dynamic_strings = (char *) &entry_parms->data.bytes[ELF_GET_NUMERIC (ELFHDR, shdr_dynstr, file_offset)]; // quick access to dynamic sections strings table
 
 
 
               // walk through the dynamic section, look for the DT_SONAME entry
 
               canonical_dylib_name = NULL; // assume none until told otherwise
 
               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)];
 
                    (ELF_GET_NUMERIC (ELFHDR, dynamic_entry, tag) != ELF_DT_NULL);
 
                    dynamic_entry = (elf_dynamic_section_entry_t *) ((uint8_t *) dynamic_entry + ELF_STRUCT_SIZE (ELFHDR, dynamic_entry)))
 
                  if (ELF_GET_NUMERIC (ELFHDR, dynamic_entry, tag) == ELF_DT_SONAME)
 
                  {
 
                     canonical_dylib_name = dynamic_strings + ELF_GET_NUMERIC (ELFHDR, dynamic_entry, value);
 
                     break;
 
                  }
 
 
 
               // do we have it ?
 
               if ((canonical_dylib_name != NULL) && (canonical_dylib_name[0] != 0))
 
               {
 
                  sprintf_s (candidate_pathname, MAXPATHLEN, "%s/%s", (entry_parms->prefix != NULL ? entry_parms->prefix : ""), canonical_dylib_name);
 
                  if (strcmp (candidate_pathname
, stored_pathname
) != 0) // claimed dylib name differs from passed name ?  
                  {
 
                     original_stored_pathname = stored_pathname; // if so, remember to create a symlink here
 
                     stored_pathname = candidate_pathname;
 
                  }
 
               }
 
            }
 
         } // end if the file we're storing is a dylib
 
 
 
         // now strip this ELF file if necessary
 
         if (!(entry_parms->extra_ino_flags & IFS_INO_PROCESSED_ELF))
 
         {
 
            Buffer_StripELFFile (&entry_parms->data, saved_ELF_sections, saved_ELF_section_count, stored_pathname); // strip the ELF file à la mkifs
 
            entry_parms->extra_ino_flags |= IFS_INO_PROCESSED_ELF; // mark this inode as a preprocessed ELF file
 
         } // end if the file is not yet a processed ELF
 
      } // end if the file we're storing is an ELF file
 
      #undef ELFHDR // undefine the macro that used to always point to the ELF header at the beginning of the file
 
   }
 
   else if (S_ISLNK (entry_parms->st_mode)) // else are we storing a symbolic link ?
 
      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);
 
   else // we must be storing a FIFO
 
   {
 
      if (strchr (entry_parms
->data.
bytes, ':') == NULL
)  
         DIE_WITH_EXITCODE (1, "device entry \"%s\" malformed (no 'dev:rdev' pair)", stored_pathname);
 
      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);
 
   }
 
 
 
   // grow filesystem entries array to hold one more slot
 
   reallocated_ptr 
= realloc (*fsentries
, (*fsentry_count 
+ 1) * sizeof (fsentry_t
)); // attempt to reallocate 
   ASSERT_WITH_ERRNO (reallocated_ptr);
 
   *fsentries = reallocated_ptr; // save reallocated pointer
 
   fsentry = &(*fsentries)[*fsentry_count]; // quick access to fs entry slot
 
   fsentry->header.extattr_offset = 0;
 
   fsentry->header.ino = entry_parms->extra_ino_flags | (++inode_count);
 
   fsentry->header.mode = entry_parms->st_mode;
 
   fsentry->header.gid = entry_parms->gid;
 
   fsentry->header.uid = entry_parms->uid;
 
   fsentry
->header.
mtime = (entry_parms
->mtime 
== UINT32_MAX 
? (uint32_t) time (NULL
) : entry_parms
->mtime
); 
   if (S_ISDIR (entry_parms->st_mode))
 
   {
 
      fsentry->u.dir.path = strdup (stored_pathname[0] == '/' ? &stored_pathname[1] : stored_pathname);
 
 
 
      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 
      fsentry->UNSAVED_was_data_written = true; // no data to save
 
   }
 
   else if (S_ISREG (entry_parms->st_mode))
 
   {
 
      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
 
      fsentry->u.file.size = (uint32_t) entry_parms->data.size;
 
      fsentry->u.file.path = strdup (stored_pathname[0] == '/' ? &stored_pathname[1] : stored_pathname);
 
      fsentry
->u.
file.
UNSAVED_databuf = malloc (entry_parms
->data.
size); 
      ASSERT_WITH_ERRNO (fsentry->u.file.UNSAVED_databuf);
 
      memcpy (fsentry
->u.
file.
UNSAVED_databuf, entry_parms
->data.
bytes, entry_parms
->data.
size);  
 
 
      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 
      fsentry->UNSAVED_was_data_written = false; // there *IS* data to save
 
   }
 
   else if (S_ISLNK (entry_parms->st_mode))
 
   {
 
      fsentry
->u.
symlink.
sym_offset = (uint16_t) (strlen (stored_pathname
[0] == '/' ? &stored_pathname
[1] : stored_pathname
) + 1); 
      fsentry->u.symlink.sym_size = (uint16_t) entry_parms->data.size;
 
      fsentry->u.symlink.path = strdup (stored_pathname[0] == '/' ? &stored_pathname[1] : stored_pathname);
 
      fsentry->u.symlink.contents = strdup (entry_parms->data.bytes);
 
      ASSERT_WITH_ERRNO (fsentry->u.symlink.contents);
 
 
 
      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
 
      fsentry->UNSAVED_was_data_written = true; // no data to save
 
   }
 
   else // necessarily a device node
 
   {
 
      fsentry
->u.
device.
dev  = strtol (entry_parms
->data.
bytes, NULL
, 0); // use strtol() to parse decimal (...), hexadecimal (0x...) and octal (0...) numbers 
      fsentry
->u.
device.
rdev = strtol (strchr (entry_parms
->data.
bytes, ':') + 1, NULL
, 0); // use strtol() to parse decimal (...), hexadecimal (0x...) and octal (0...) numbers 
      fsentry->u.device.path = strdup (stored_pathname[0] == '/' ? &stored_pathname[1] : stored_pathname);
 
 
 
      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 
      fsentry->UNSAVED_was_data_written = true; // no data to save
 
   }
 
   (*fsentry_count)++;
 
 
 
   // should we also add a symlink to this entry ? (in case we stored a dylib file under its canonical name)
 
   if (original_stored_pathname != NULL)
 
   {
 
      entry_parms->is_compiled_bootscript = false;
 
      entry_parms->should_autosymlink_dylib = false;
 
      entry_parms->should_follow_symlinks = false;
 
      entry_parms->st_mode = S_IFLNK | 0777; // NOTE: mkifs stores symlink permissions as rwxrwxrwx !
 
      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
 
      last_dirsep 
= strrchr (stored_pathname
, '/'); 
      old_data = entry_parms->data.bytes; // backup previous data pointer
 
      entry_parms->data.bytes = (uint8_t *) (last_dirsep == NULL ? stored_pathname : last_dirsep + 1); // store symlink target in dirent data
 
      entry_parms
->data.
size = strlen (entry_parms
->data.
bytes); 
      add_fsentry (fsentries, fsentry_count, entry_parms, original_stored_pathname, NULL);
 
      entry_parms->data.bytes = old_data; // restore previous data pointer so that it can be freed normally
 
   }
 
 
 
   return (*fsentry_count);
 
}
 
 
 
 
 
static int fsentry_compare_pathnames_cb (const void *a, const void *b)
 
{
 
   // qsort() callback that compares two imagefs filesystem entries and sort them alphabetically by pathname
 
 
 
   const fsentry_t *entry_a = (const fsentry_t *) a;
 
   const fsentry_t *entry_b = (const fsentry_t *) b;
 
   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)));
 
   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)));
 
   return (strcmp (pathname_a
, pathname_b
));  
}
 
 
 
 
 
static void parse_line (FILE *buildfile_fp, char *line_buffer, fsentry_t **fsentries, size_t *fsentry_count, parms_t *default_parms)
 
{
 
   thread_local static char path_on_buildhost[MAXPATHLEN] = "";
 
   thread_local static char path_in_ifs[MAXPATHLEN] = "";
 
   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)
 
 
 
   bool should_discard_inline_contents;
 
   bool is_quoted_context;
 
   bool is_escaped_char;
 
   struct stat stat_buf;
 
   struct tm utc_time;
 
   void *reallocated_ptr;
 
   size_t allocated_size;
 
   size_t string_len;
 
   char *specifiedpathname_start;
 
   char *attrblock_start;
 
   char *write_ptr;
 
   char *line_ptr;
 
   char *value;
 
   char *token;
 
   char *sep;
 
   char *ctx;
 
   int read_char;
 
 
 
   line_ptr = line_buffer;
 
   while ((*line_ptr 
!= 0) && isspace (*line_ptr
))  
      line_ptr++; // skip leading spaces
 
 
 
   if ((*line_ptr == 0) || (*line_ptr == '#'))
 
      return; // skip empty or comment lines
 
 
 
   string_len 
= (int) strlen (line_buffer
); 
   if ((string_len > 0) && (line_buffer[string_len - 1] == '\n'))
 
      line_buffer[string_len - 1] = 0; // chop off newline for easier debug output
 
 
 
   // reset entry values
 
   memcpy (&entry_parms
, default_parms
, sizeof (parms_t
));  
   path_in_ifs[0] = 0;
 
   path_on_buildhost[0] = 0;
 
   should_discard_inline_contents = false;
 
 
 
   // does this line start with an attribute block ?
 
   if (*line_ptr == '[')
 
   {
 
      line_ptr++; // skip the leading square bracket
 
      attrblock_start = line_ptr; // remember where it starts
 
      is_quoted_context = false;
 
      while ((*line_ptr != 0) && !((*line_ptr == ']') && (line_ptr[-1] != '\\') && !is_quoted_context))
 
      {
 
         if (*line_ptr == '"')
 
            is_quoted_context ^= true; // remember when we're between quotes
 
         else if (!is_quoted_context && (*line_ptr == ' '))
 
            *line_ptr = RECORD_SEP[0]; // turn all spaces outside quoted contexts into an ASCII record separator to ease token splitting
 
         line_ptr++; // reach the next unescaped closing square bracket
 
      }
 
      if (*line_ptr != ']')
 
      {
 
         LOG ("warning", 0, "syntax error in \"%s\" line %d: unterminated attributes block (skipping)", buildfile_pathname, lineno);
 
         return; // invalid attribute block, skip line
 
      }
 
      *line_ptr = 0; // end the attribute block so that it is a parsable C string
 
 
 
      // now parse the attribute tokens
 
      // DOCUMENTATION: https://www.qnx.com/developers/docs/8.0/com.qnx.doc.neutrino.utilities/topic/m/mkifs.html#mkifs__description
 
      token = strtok_r (attrblock_start, RECORD_SEP, &ctx);
 
      while (token != NULL)
 
      {
 
         // evaluate attribute token
 
         #define REACH_TOKEN_VALUE() do { value = strchr (token, '=') + 1; if (*value == '"') value++; } while (0)
 
         if (false) {}
 
         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.)  
         else if (strncmp (token
, "uid=",     4) == 0) { REACH_TOKEN_VALUE 
(); entry_parms.
uid     = (int) read_integer 
(value
); }  
         else if (strncmp (token
, "gid=",     4) == 0) { REACH_TOKEN_VALUE 
(); entry_parms.
gid     = (int) read_integer 
(value
); }  
         else if (strncmp (token
, "dperms=",  7) == 0) { REACH_TOKEN_VALUE 
(); entry_parms.
dperms  = (int) read_integer 
(value
); }  
         else if (strncmp (token
, "perms=",   6) == 0) { REACH_TOKEN_VALUE 
(); entry_parms.
perms   = (int) read_integer 
(value
); }  
         else if (strncmp (token
, "type=",    5) == 0) { REACH_TOKEN_VALUE 
();   
            if      (strcmp (value
, "dir")  == 0) entry_parms.
st_mode = S_IFDIR
;  
            else if (strcmp (value
, "file") == 0) entry_parms.
st_mode = S_IFREG
;  
            else if (strcmp (value
, "link") == 0) entry_parms.
st_mode = S_IFLNK
;  
            else if (strcmp (value
, "fifo") == 0) entry_parms.
st_mode = S_IFIFO
;  
            else DIE_WITH_EXITCODE (1, "invalid 'type' attribute in \"%s\" line %d: '%s'", buildfile_pathname, lineno, value);
 
         }
 
         else if (strncmp (token
, "image=",   6) == 0) { REACH_TOKEN_VALUE 
();  
            image_base = (uint32_t) read_integer (value); // read image base address
 
            if ((sep 
= strchr (value
, '-')) != NULL
) image_end       
= (uint32_t) read_integer 
(sep 
+ 1); // if we have a dash, read optional image end (TODO: check this value and produce an error in the relevant case. Not important.)  
            if ((sep 
= strchr (value
, ',')) != NULL
) image_maxsize   
= (uint32_t) read_integer 
(sep 
+ 1); // if we have a comma, read optional image max size  
            if ((sep 
= strchr (value
, '=')) != NULL
) image_totalsize 
= (uint32_t) read_integer 
(sep 
+ 1); // if we have an equal sign, read optional image padding size  
            if ((sep 
= strchr (value
, '%')) != NULL
) image_align     
= (uint32_t) read_integer 
(sep 
+ 1); // if we have a modulo sign, read optional image aligmnent  
            LOG_INFO ("image 0x%x-0x%x maxsize %d totalsize %d align %d", image_base, image_end, image_maxsize, image_totalsize, image_align);
 
         }
 
         else if (strncmp (token
, "virtual=", 8) == 0) { REACH_TOKEN_VALUE 
();  
            if ((bootfile_pathname == NULL) || (startupfile_pathname == NULL) || (kernelfile_pathname == NULL)) // HACK until I figure out how to re-create them
 
               DIE_WITH_EXITCODE (1, "creating bootable images require the --bootfile, --startupfile and --kernelfile command-line options in \"%s\" line %d", buildfile_pathname, lineno);
 
            if ((sep 
= strchr (value
, ',')) != NULL
) // do we have a comma separating (optional) processor and boot file name ?  
            {
 
               *sep = 0;
 
               strcpy_s (image_processor, sizeof (image_processor), value); // save processor
 
               value = sep + 1;
 
            }
 
            //sprintf (image_bootfile, "%s/%s/boot/sys/%s.boot", QNX_TARGET, image_processor, value); // save preboot file name (TODO: we should search in MKIFS_PATH instead of this. Not important.)
 
            //strcpy (image_bootfile, bootfile_pathname); // FIXME: HACK
 
            if (stat (bootfile_pathname, &stat_buf) != 0)
 
               DIE_WITH_EXITCODE 
(1, "unable to stat the boot file \"%s\" specified in \"%s\" line %d: %s", bootfile_pathname
, buildfile_pathname
, lineno
, strerror (errno
)); 
            bootfile_size = stat_buf.st_size; // save preboot file size
 
            LOG_INFO ("processor \"%s\" bootfile \"%s\"\n", image_processor, bootfile_pathname);
 
#if 1
 
            // ######################################################################################################################################################################################################################################
 
            // # FIXME: figure out how to re-create it: linker call involved
 
            // # $ x86_64-pc-nto-qnx8.0.0-ld --sysroot=${QNX_TARGET}/x86_64/ -T${QNX_TARGET}/x86_64/lib/nto.link --section-start .text=0xffff800000001000 --no-relax ${QNX_TARGET}/x86_64/boot/sys/procnto-smp-instr -o procnto-smp-instr.sym.UNSTRIPPED
 
            // ######################################################################################################################################################################################################################################
 
//               if (!Buffer_ReadFromFile (&entry_parms.data, kernelfile_pathname))
 
//                  DIE_WITH_EXITCODE (1, "unable to read precompiled kernel file \"%s\" specified in --kernelfile argument: %s", kernelfile_pathname, strerror (errno));
 
#else // nonworking
 
            strcpy (path_on_buildhost
, "procnto-smp-instr");  
#endif // nonworking
 
         }
 
         else if (strncmp (token
, "mtime=", 6) == 0) { REACH_TOKEN_VALUE 
(); if (strcmp (value
, "*") == 0) entry_parms.
mtime = UINT32_MAX
; else {  
               // value *must* be "YYYY-MM-DD-HH:MM:SS" by specification
 
               memset (&utc_time
, 0, sizeof (utc_time
));  
               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)
 
               {
 
                  LOG_WARNING ("syntax error in \"%s\" line %d: mtime specification not in YYYY-MM-DD-HH:MM:SS format (skipping)", buildfile_pathname, lineno);
 
                  continue; // invalid attribute block, skip line
 
               }
 
               utc_time.tm_mon--; // convert month from [1-12] to [0-11]
 
               entry_parms.
mtime = (uint32_t) mktime (&utc_time
); 
            }
 
         }
 
         else if (strcmp (token
, "+script")     == 0) {  
            entry_parms.is_compiled_bootscript = true;
 
            ASSERT_WITH_ERRNO (Buffer_InitWithByteArray (&entry_parms.data, INITIAL_STARTUP_SCRIPT)); // FIXME: HACK until the script compiler is implemented
 
            should_discard_inline_contents = true; // remember we already have data (so as to discard the inline block's contents)
 
         }
 
         else if (strcmp (token
, "-script")     == 0) entry_parms.
is_compiled_bootscript = false;  
         else if (strcmp (token
, "+followlink") == 0) entry_parms.
should_follow_symlinks = true;  
         else if (strcmp (token
, "-followlink") == 0) entry_parms.
should_follow_symlinks = false;  
         else if (strcmp (token
, "+autolink")   == 0) entry_parms.
should_autosymlink_dylib = true;  
         else if (strcmp (token
, "-autolink")   == 0) entry_parms.
should_autosymlink_dylib = false;  
         else if (strcmp (token
, "+keeplinked") == 0) entry_parms.
should_keep_ld_output = true;  
         else if (strcmp (token
, "-keeplinked") == 0) entry_parms.
should_keep_ld_output = false;  
         else LOG_WARNING ("unimplemented attribute in \"%s\" line %d: '%s'", buildfile_pathname, lineno, token);
 
         #undef REACH_TOKEN_VALUE
 
 
 
         token = strtok_r (NULL, RECORD_SEP, &ctx); // proceed to next attribute token
 
      }
 
 
 
      line_ptr++; // reach the next character
 
      while ((*line_ptr 
!= 0) && isspace (*line_ptr
))  
         line_ptr++; // skip leading spaces
 
 
 
      // are we at the end of the line ? if so, it means the attribute values that are set should become the default
 
      if ((*line_ptr == 0) || (*line_ptr == '#'))
 
      {
 
         #define APPLY_DEFAULT_ATTR_NUM(attr,descr,fmt) do { if (entry_parms.attr != default_parms->attr) { \
 
               LOG_INFO ("changing default " descr " from " fmt " to " fmt " by attribute at \"%s\" line %d", default_parms->attr, entry_parms.attr, buildfile_pathname, lineno); \
 
               default_parms->attr = entry_parms.attr; \
 
            } } while (0)
 
         #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))) { \
 
            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); \
 
               default_parms->attr = entry_parms.attr; \
 
            } } while (0)
 
         //APPLY_DEFAULT_ATTR_STR (new_cwd,                  "current working directory",       "\"%s\"");
 
         APPLY_DEFAULT_ATTR_STR (search,                   "search path list",                "\"%s\"");
 
         APPLY_DEFAULT_ATTR_STR (prefix,                   "prefix",                          "\"%s\"");
 
         APPLY_DEFAULT_ATTR_NUM (dperms,                   "directory permissions",           "0%o");
 
         APPLY_DEFAULT_ATTR_NUM (perms,                    "file permissions",                "0%o");
 
         APPLY_DEFAULT_ATTR_NUM (uid,                      "owner ID",                        "%d");
 
         APPLY_DEFAULT_ATTR_NUM (gid,                      "group ID",                        "%d");
 
         APPLY_DEFAULT_ATTR_NUM (st_mode,                  "inode type",                      "0%o");
 
         APPLY_DEFAULT_ATTR_NUM (is_compiled_bootscript,   "compiled script state",           "%d");
 
         APPLY_DEFAULT_ATTR_NUM (should_follow_symlinks,   "symlink resolution",              "%d");
 
         APPLY_DEFAULT_ATTR_NUM (should_autosymlink_dylib, "dylib canonical name symlinking", "%d");
 
         APPLY_DEFAULT_ATTR_NUM (should_keep_ld_output,    "linker output preservation",      "%d");
 
         #undef APPLY_DEFAULT_ATTR_STR
 
         #undef APPLY_DEFAULT_ATTR_NUM
 
         return; // end of line reached, proceed to the next line
 
      }
 
      // end of attributes parsing
 
   } // end of "this line starts with an attributes block"
 
 
 
   // there's data in this line. We expect a filename in the IFS. Read it and unescape escaped characters
 
   string_len = sprintf_s (path_in_ifs, sizeof (path_in_ifs), "%s", (entry_parms.prefix != NULL ? entry_parms.prefix : ""));
 
   while ((string_len > 0) && (path_in_ifs[string_len - 1] == '/'))
 
      string_len--; // chop off any trailing slashes from prefix
 
   write_ptr = &path_in_ifs[string_len];
 
   *write_ptr++ = '/'; // add ONE trailing slash
 
   specifiedpathname_start = write_ptr; // remember the specified pathname will start here
 
   is_quoted_context = (*line_ptr == '"');
 
   if (is_quoted_context)
 
      line_ptr++; // skip a possible initial quote
 
   if (*line_ptr == '/')
 
   {
 
      LOG_WARNING ("paths in the IFS file should not begin with a leading '/' in \"%s\" line %d", buildfile_pathname, lineno);
 
      line_ptr++; // consistency check: paths in the IFS should not begin with a '/'
 
   }
 
   while ((*line_ptr 
!= 0) && ((!is_quoted_context 
&& (*line_ptr 
!= '=') && !isspace (*line_ptr
)) || (is_quoted_context 
&& (*line_ptr 
== '"'))))  
   {
 
      if (*line_ptr == '\\')
 
      {
 
         line_ptr++;
 
         *write_ptr++ = *line_ptr; // unescape characters that are escaped with '\'
 
      }
 
      else
 
         *write_ptr++ = *line_ptr;
 
      line_ptr++;
 
   }
 
   *write_ptr = 0; // terminate the string
 
   if (is_quoted_context && (*line_ptr == '"'))
 
      line_ptr++; // skip a possible final quote
 
 
 
   // we reached a space OR an equal sign
 
   while ((*line_ptr 
!= 0) && isspace (*line_ptr
))  
      line_ptr++; // skip optional spaces after the filename in the IFS
 
 
 
   // do we have an equal sign ?
 
   if (*line_ptr == '=') // we must be creating either a directory or a file, do we have an equal sign ?
 
   {
 
      line_ptr++; // skip the equal sign
 
      while ((*line_ptr 
!= 0) && isspace (*line_ptr
))  
         line_ptr++; // skip optional spaces after the equal sign
 
 
 
      if (*line_ptr == 0)
 
      {
 
         LOG_WARNING ("syntax error in \"%s\" line %d: missing data specification after equal sign (skipping)", buildfile_pathname, lineno);
 
         return; // invalid symlink specification, skip line
 
      }
 
 
 
      // read the host system's path, it may be either a path or a contents definition. Is it a content definition ?
 
      if (*line_ptr == '{')
 
      {
 
         allocated_size = 0;
 
 
 
         line_ptr++; // skip the leading content definition
 
         is_escaped_char = false;
 
         for (;;)
 
         {
 
            read_char 
= fgetc (buildfile_fp
); 
            if (read_char == EOF)
 
               DIE_WITH_EXITCODE (1, "syntax error in \"%s\" line %d: unterminated contents block (end of file reached)", buildfile_pathname, lineno); // invalid contents block
 
            else if ((read_char == '\\') && !is_escaped_char)
 
               is_escaped_char = true; // remember the next char is escaped
 
            else if ((read_char == '}') && !is_escaped_char)
 
               break; // found an unescaped closing bracked, stop parsing
 
            else
 
            {
 
               is_escaped_char = false; // any other char, meaning the next one will not be escaped
 
               if (!should_discard_inline_contents) // only store the contents if we do NOT know the data yet
 
               {
 
                  if (entry_parms.data.size == allocated_size) // reallocate in 4 kb blocks
 
                  {
 
                     reallocated_ptr 
= realloc (entry_parms.
data.
bytes, allocated_size 
+ 4096); 
                     ASSERT_WITH_ERRNO (reallocated_ptr);
 
                     entry_parms.data.bytes = reallocated_ptr;
 
                     allocated_size += 4096;
 
                  }
 
                  entry_parms.data.bytes[entry_parms.data.size++] = read_char;
 
               }
 
               if (read_char == '\n')
 
                  lineno++; // update line counter as we parse the inline content
 
            }
 
         } // end for
 
      }
 
      else // not a content definition between { brackets }, must be either a pathname on the build host, or the target of a symlink
 
      {
 
         is_quoted_context = (*line_ptr == '"');
 
         if (is_quoted_context)
 
            line_ptr++; // skip a possible initial quote
 
         specifiedpathname_start = line_ptr; // remember where the specified pathname starts
 
         write_ptr = line_ptr; // now unescape all characters
 
         while ((*line_ptr 
!= 0) && ((!is_quoted_context 
&& !isspace (*line_ptr
)) || (is_quoted_context 
&& (*line_ptr 
== '"'))))  
         {
 
            if (*line_ptr == '\\')
 
            {
 
               line_ptr++;
 
               *write_ptr++ = *line_ptr; // unescape characters that are escaped with '\'
 
            }
 
            else
 
               *write_ptr++ = *line_ptr;
 
            line_ptr++;
 
         }
 
         *write_ptr = 0; // terminate the string
 
         if (is_quoted_context && (*line_ptr == '"'))
 
            line_ptr++; // skip a possible final quote
 
 
 
         if (S_ISLNK (entry_parms.st_mode)) // are we storing a symlink ?
 
            ASSERT_WITH_ERRNO (Buffer_InitWithCString (&entry_parms.data, specifiedpathname_start)); // if so, store the symlink target as the dirent's blob data
 
         else // it's a build host filesystem path
 
            strcpy_s (path_on_buildhost, sizeof (path_on_buildhost), specifiedpathname_start); // the path on the build host is given after the equal sign
 
      }
 
   }
 
   else // no equal sign, meaning the file will have the same name on the build host filesystem
 
   {
 
      // consistency check: symlinks MUST have an equal sign
 
      if (entry_parms.st_mode == S_IFLNK)
 
      {
 
         LOG_WARNING ("syntax error in \"%s\" line %d: missing equal sign and symlink target (skipping)", buildfile_pathname, lineno);
 
         return; // invalid symlink specification, skip line
 
      }
 
 
 
      strcpy_s (path_on_buildhost, sizeof (path_on_buildhost), specifiedpathname_start); // the path on the build host is the one specified
 
      sep 
= strrchr (specifiedpathname_start
, '/'); 
      if (sep != NULL)
 
         memmove (specifiedpathname_start
, sep 
+ 1, strlen (sep 
+ 1) + 1); // the path in the IFS will be the BASENAME of the path specified (after the prefix)  
   }
 
 
 
   // now add this entry to the image filesystem
 
   if (S_ISDIR (entry_parms.st_mode))
 
      entry_parms.st_mode |= entry_parms.dperms;
 
   else if (S_ISLNK (entry_parms.st_mode))
 
      entry_parms.st_mode |= 0777; // NOTE: mkifs sets symlink permissions to rwxrwxrwx !?
 
   else // file or device node
 
      entry_parms.st_mode |= entry_parms.perms;
 
 
 
   add_fsentry (fsentries, fsentry_count, &entry_parms, path_in_ifs, path_on_buildhost); // and add filesystem entry
 
 
 
   if (entry_parms.data.bytes != NULL)
 
      free (entry_parms.
data.
bytes); // if blob data was allocated, free it  
 
 
   return; //  finished parsing that line
 
}
 
 
 
 
 
int main (int argc, char **argv)
 
{
 
   // program entrypoint
 
 
 
   typedef struct ifs_offsets_s
 
   {
 
      size_t startupheader;
 
      size_t startuptrailer;
 
      size_t imageheader;
 
      size_t imagedir;
 
      size_t imagetrailer;
 
   } ifs_offsets_t;
 
   typedef struct ifs_s
 
   {
 
      buffer_t data;
 
      ifs_offsets_t offsets;
 
      size_t final_size; // final size: not known (because not set) until everything has been written
 
   } ifs_t;
 
 
 
   static startup_header_t startup_header = { 0 }; // output IFS's startup header
 
   static startup_trailer_v2_t startup_trailer = { 0 }; // output IFS's startup trailer (version 2, with SHA-512 checksum and int32 checksum)
 
   static image_header_t image_header = { 0 }; // output IFS's imagefs header
 
   static image_trailer_v2_t image_trailer = { 0 }; // output IFS's imagefs trailer (version 2, with SHA-512 checksum and int32 checksum)
 
   static fsentry_t *fsentries = NULL; // output IFS's filesystem entries
 
   static size_t fsentry_count = 0; // number of entries in the IFS filesystem
 
   static parms_t default_parms = { // default parameters for a filesystem entry
 
      .dperms = 0755,
 
      .perms = 0644,
 
      .uid = 0,
 
      .gid = 0,
 
      .st_mode = S_IFREG,
 
      .mtime = UINT32_MAX,
 
      .mtime_for_inline_files = UINT32_MAX,
 
      .prefix = "/proc/boot",
 
      .should_follow_symlinks = true, // [+|-followlink]
 
      .should_autosymlink_dylib = true, // [+|-autolink]
 
      .is_compiled_bootscript = false, // [+|-script]
 
      .extra_ino_flags = 0,
 
      .search = NULL,
 
      .data = { NULL, 0 }
 
   };
 
   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)
 
 
 
   char path_on_buildhost[MAXPATHLEN] = "";
 
   char path_in_ifs[MAXPATHLEN] = "";
 
   char *ifs_pathname = NULL;
 
   void *reallocated_ptr;
 
   size_t reallocated_size;
 
   size_t available_space;
 
   size_t fsentry_index;
 
   size_t largest_index;
 
   size_t largest_size;
 
   size_t imgdir_size;
 
   size_t curr_offset;
 
   ifs_t ifs = { 0 };
 
   int32_t checksum;
 
   char *first_pathname = NULL;
 
   char *second_pathname = NULL;
 
   char *sep;
 
   int arg_index;
 
   bool is_quoted_context = false;
 
   bool is_escaped_char = false;
 
   bool should_discard_inline_contents = false;
 
   bool want_info = false;
 
   bool want_everything = false;
 
   bool want_help = false;
 
   bool want_dump = false;
 
   bool want_strip = false;
 
   bool want_hexdump = false;
 
   bool is_foreign_endianness;
 
   FILE *buildfile_fp;
 
 
 
   // initialize stuff
 
   saved_ELF_sections 
= (char **) malloc (4 * sizeof (char *)); 
   ASSERT_WITH_ERRNO (saved_ELF_sections);
 
   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
 
   saved_ELF_sections[1] = ".gnu_debuglink";
 
   saved_ELF_sections[2] = "QNX_usage";
 
   saved_ELF_sections[3] = ".note.gnu.build-id"; // undocumented by QNX, but nonetheless preserved
 
   saved_ELF_section_count = 4;
 
 
 
   // parse arguments
 
   for (arg_index = 1; arg_index < argc; arg_index++)
 
   {
 
      if ((strcmp (argv
[arg_index
], "--bootfile") == 0) && (arg_index 
+ 1 < argc
)) // --bootfile path/to/blob.bin  
         bootfile_pathname = argv[++arg_index];
 
      else if ((strcmp (argv
[arg_index
], "--startupfile") == 0) && (arg_index 
+ 1 < argc
)) // --startupfile path/to/blob.bin@0x1030  
      {
 
         sep 
= strchr (argv
[++arg_index
], '@'); 
         if ((sep == NULL) || (sep[1] == 0))
 
            DIE_WITH_EXITCODE (1, "the --startupfile arguments expects <pathname>@<entrypoint_from_image_base>");
 
         *sep = 0;
 
         startupfile_pathname = argv[arg_index];
 
         startupfile_ep_from_imagebase = (size_t) read_integer (sep + 1);
 
      }
 
      else if ((strcmp (argv
[arg_index
], "--kernelfile") == 0) && (arg_index 
+ 1 < argc
)) // --kernelfile path/to/blob.bin@0x32000  
      {
 
         sep 
= strchr (argv
[++arg_index
], '@'); 
         if ((sep == NULL) || (sep[1] == 0))
 
            DIE_WITH_EXITCODE (1, "the --kernelfile arguments expects <pathname>@<fileoffset>");
 
         *sep = 0;
 
         kernelfile_pathname = argv[arg_index];
 
         kernelfile_offset = (size_t) read_integer (sep + 1);
 
      }
 
      else if (strcmp (argv
[arg_index
], "-n") == 0)  
         default_parms.mtime_for_inline_files = 0; // inline files should have a mtime set to zero
 
      else if (strcmp (argv
[arg_index
], "-nn") == 0)  
      {
 
         default_parms.mtime = 0; // *all* files should have a mtime set to zero
 
         default_parms.mtime_for_inline_files = 0;
 
      }
 
      else if ((strcmp (argv
[arg_index
], "--outdir") == 0) && (arg_index 
+ 1 < argc
)) // --outdir path  
         second_pathname = argv[++arg_index];
 
      else if ((strcmp (argv
[arg_index
], "--outfile") == 0) && (arg_index 
+ 1 < argc
)) // --outfile pathname  
         second_pathname = argv[++arg_index];
 
      else if (strcmp (argv
[arg_index
], "--info") == 0)  
         want_info = true;
 
      else if (strcmp (argv
[arg_index
], "--dump") == 0)  
         want_dump = true;
 
      else if (strcmp (argv
[arg_index
], "--hexdump") == 0) // voluntarily undocumented  
         want_hexdump = true;
 
      else if (strcmp (argv
[arg_index
], "--strip") == 0)  
         want_strip = true;
 
      else if (strcmp (argv
[arg_index
], "--everything") == 0)  
         want_everything = true;
 
      else if (strncmp (argv
[arg_index
], "-v", 2) == 0) // -v[....]  
         verbose_level 
+= (int) strlen (argv
[arg_index
] + 1); // increase verbosity by the number of characters in this flag 
      else if ((strcmp (argv
[arg_index
], "-l") == 0) && (arg_index 
+ 1 < argc
))  
         arg_index++; // these args will be parsed once the build file is open
 
      else if ((strcmp (argv
[arg_index
], "-r") == 0) && (arg_index 
+ 1 < argc
))  
      {
 
         reallocated_size 
= (SEARCH_PATH 
!= NULL 
? strlen (SEARCH_PATH
) + 1 : 0) + strlen (argv
[arg_index 
+ 1]) + 1; 
         reallocated_ptr 
= realloc (SEARCH_PATH
, reallocated_size
); // grow search prefixes array 
         ASSERT_WITH_ERRNO (reallocated_ptr);
 
         if (SEARCH_PATH != NULL)
 
            strcat_s (reallocated_ptr, reallocated_size, PATH_SEP);
 
         strcat_s (reallocated_ptr, reallocated_size, argv[++arg_index]); // stack up another search prefix
 
         SEARCH_PATH = reallocated_ptr;
 
      }
 
      else if ((strcmp (argv
[arg_index
], "-s") == 0) && (arg_index 
+ 1 < argc
))  
      {
 
         reallocated_ptr 
= realloc (saved_ELF_sections
, (saved_ELF_section_count 
+ 1) * sizeof (char *)); // grow ELF sections array 
         ASSERT_WITH_ERRNO (reallocated_ptr);
 
         saved_ELF_sections = reallocated_ptr;
 
         saved_ELF_sections[saved_ELF_section_count++] = argv[++arg_index]; // stack up another ELF section name to preserve
 
      }
 
      else if ((strcmp (argv
[arg_index
], "-?") == 0) || (strcmp (argv
[arg_index
], "--help") == 0))  
         want_help = true;
 
      else if (first_pathname == NULL)
 
         first_pathname = argv[arg_index];
 
      else if (second_pathname == NULL)
 
         second_pathname = argv[arg_index];
 
      else
 
         DIE_WITH_EXITCODE (1, "unrecognized option: '%s'", argv[arg_index]);
 
   }
 
 
 
   // do we not have enough information to run ?
 
   if (want_help || (first_pathname == NULL) || (!want_info && !want_dump && !want_hexdump && !want_strip && (second_pathname == NULL)))
 
   {
 
      FILE *out = (want_help ? stdout : stderr); // select the right output channel
 
      fprintf (out
, "ifstool - QNX in-kernel filesystem creation utility by Pierre-Marie Baty <pm@pmbaty.com>\n");  
      fprintf (out
, "          version " VERSION_FMT_YYYYMMDD 
"\n", VERSION_ARG_YYYYMMDD
);  
      if (!want_help)
 
         fprintf (out
, "error: missing parameters\n");  
      fprintf (out
, "    ifstool --info [--everything] <ifs file>\n");  
      fprintf (out
, "    ifstool --dump [--outdir <path>] <ifs file>\n");  
      fprintf (out
, "    ifstool --strip [--outfile <pathname>] <ELF file>\n");  
      fprintf (out
, "    ifstool [-?|--help]\n");  
      fprintf (out
, "    ifstool [--bootfile <pathname>] [--startupfile <pathname>@<EP_from_imgbase>] [--kernelfile <pathname>@<fileoffs>] [-l inputline] [-n[n]] [-r rootdir] [-v[...]] <buildfile> <outfile>\n");  
      fprintf (out
, "NOTE: the compiler mode requires predigested boot, startup and kernel files produced by mkifs.\n");  
      fprintf (out
, "    -?       Display some help information.\n");  
//      fprintf (out, "    -a .ext  Append a suffix to symbol files generated via [+keeplinked].\n");
 
      fprintf (out
, "    -l line  Process line before interpreting the buildfile. Input lines given\n");  
      fprintf (out
, "             to mkifs should be quoted to prevent interpretation by the shell\n");  
      fprintf (out
, "             (especially as mkifs input lines often contain spaces). Multiple\n");  
      fprintf (out
, "             -l options are processed in the order specified. No default.\n");  
      fprintf (out
, "    -n[n]    Force the modification times of all inline files to be 0. If you\n");  
      fprintf (out
, "             specify -nn, mkifs sets the modification times of all files to 0.\n");  
      fprintf (out
, "             When mkifs adds files to an IFS image, it uses the timestamp info\n");  
      fprintf (out
, "             from the file on the host machine. If mkifs is creating an inline\n");  
      fprintf (out
, "             file (which doesn't exist on the host machine), it must generate\n");  
      fprintf (out
, "             its own timestamp information. By default, it's the time at which\n");  
      fprintf (out
, "             the image is generated. This results in different checksum values\n");  
      fprintf (out
, "             for two identical builds, because the file's times are different.\n");  
      fprintf (out
, "             If you use -n, the checksum value is the same on all identical\n");  
      fprintf (out
, "             builds. The -nn option addresses a quirk in NTFS with daylight\n");  
      fprintf (out
, "             savings time. This forces the modification time for all files in\n");  
      fprintf (out
, "             the IFS image to be set to 0. This ensures that subsequent builds\n");  
      fprintf (out
, "             of the same IFS image have the same checksum.");  
//      fprintf (out, "    -o dir   Specify a directory to be used for all permanent build artifacts,\n");
 
//      fprintf (out, "             other than the output image itself. The most common example is\n");
 
//      fprintf (out, "             the .sym files generated by the [+keeplinked] attribute.\n");
 
//      fprintf (out, "    -p file  Apply patching instructions from this file.\n");
 
      fprintf (out
, "    -r dir   When searching for host files to be included in the image, search\n");  
      fprintf (out
, "             the default paths used for storing binaries within the specified\n");  
      fprintf (out
, "             directory before searching the default paths within $QNX_TARGET.\n");  
      fprintf (out
, "             You can define multiple -r options; each adds a set of paths to\n");  
      fprintf (out
, "             search for files. The -r options are evaluated from left to right\n");  
      fprintf (out
, "             meaning the paths prefixed with the first (leftmost) rootdir are\n");  
      fprintf (out
, "             searched first, then those prefixed with the second rootdir, and\n");  
      fprintf (out
, "             Normally, mkifs searches any paths defined in $MKIFS_PATH when\n");  
      fprintf (out
, "             it was called and then the default paths within $QNX_TARGET. The\n");  
      fprintf (out
, "             default paths are based on the CPU architecture specified by\n");  
      fprintf (out
, "             $PROCESSOR and $PROCESSOR_BASE. If you specify -r options, mkifs\n");  
      fprintf (out
, "             searches the default paths prefixed with each dir variable before\n");  
      fprintf (out
, "             searching those within $QNX_TARGET. These paths are:\n");  
      fprintf (out
, "               dir/${PROCESSOR}/sbin\n");  
      fprintf (out
, "               dir/${PROCESSOR}/usr/sbin\n");  
      fprintf (out
, "               dir/${PROCESSOR}/boot/sys\n");  
      fprintf (out
, "               dir/${PROCESSOR_BASE}/boot/sys\n");  
      fprintf (out
, "               dir/${PROCESSOR}/bin\n");  
      fprintf (out
, "               dir/${PROCESSOR}/usr/bin\n");  
      fprintf (out
, "               dir/${PROCESSOR}/lib\n");  
      fprintf (out
, "               dir/${PROCESSOR}/lib/dll\n");  
      fprintf (out
, "               dir/${PROCESSOR}/usr/lib\n");  
      fprintf (out
, "             NOTE: The structure of the directory paths under dir must be\n");  
      fprintf (out
, "             identical to that of the default paths under $QNX_TARGET, but the\n");  
      fprintf (out
, "             root dir itself may be any path you choose. For example, if you\n");  
      fprintf (out
, "             wanted to include /scratch/aarch64le/sbin/devb-sata, you would\n");  
      fprintf (out
, "             specify a -r option like this:\n");  
      fprintf (out
, "             Note that you don't include $PROCESSOR or $PROCESSOR_BASE in dir.\n");  
      fprintf (out
, "    -s name  Don't strip the named section from ELF executables when creating\n");  
      fprintf (out
, "             an IFS image. You can use this option more than once to specify\n");  
      fprintf (out
, "             additional sections. By default, mkifs doesn't strip:\n");  
      fprintf (out
, "               .gnu_debuglink - the name and checksum of the debug info file\n");  
      fprintf (out
, "               QNX_info       - build properties\n");  
      fprintf (out
, "               QNX_usage      - usage message\n");  
      fprintf (out
, "             You can use the keepsection attribute to specify the sections\n");  
      fprintf (out
, "             that are not to be stripped from specific files in the image. For\n");  
      fprintf (out
, "             files in the bootstrap section (like startup or procnto), the\n");  
      fprintf (out
, "             global keepsection list affected by -s does not apply to these\n");  
      fprintf (out
, "             files. For them, only the QNX_info section is kept.\n");  
      fprintf (out
, "    -v[v..]  Operate verbosely. Specifying additional v options increases the\n");  
      exit (want_help 
? 0 : 1);  
   }
 
 
 
   // do we want info about a particular IFS ? if so, dissecate it
 
   if (want_info)
 
      exit (dump_ifs_info 
(first_pathname
, want_everything
));  
 
 
   // else do we want to dump its contents ? if so, do so
 
   else if (want_dump)
 
      exit (dump_ifs_contents 
(first_pathname
, (second_pathname 
!= NULL 
? second_pathname 
: ".")));  
 
 
   // else do we want to hex dump a file ? (this is voluntarily undocumented)
 
   else if (want_hexdump)
 
      exit (dump_file_hex 
(first_pathname
));  
 
 
   // else do we want to strip an ELF file ? if so, do so
 
   else if (want_strip)
 
   {
 
      buffer_t file;
 
      ASSERT 
(Buffer_ReadFromFile 
(&file
, first_pathname
), "can't open \"%s\" for reading: %s", first_pathname
, strerror (errno
)); 
      ASSERT 
(Buffer_StripELFFile 
(&file
, saved_ELF_sections
, saved_ELF_section_count
, first_pathname
), "error stripping \"%s\": %s", first_pathname
, strerror (errno
)); 
      ASSERT_WITH_ERRNO (Buffer_WriteToFile (&file, (second_pathname != NULL ? second_pathname : "<stdout>")));
 
   }
 
 
 
   // we want to CREATE an IFS file
 
   buildfile_pathname = first_pathname; // assign the pathnames properly
 
   ifs_pathname = second_pathname;
 
 
 
   // make sure we have ${QNX_TARGET} pointing somewhere
 
   QNX_TARGET 
= getenv ("QNX_TARGET"); 
   if (QNX_TARGET == NULL)
 
      DIE_WITH_EXITCODE (1, "the QNX_TARGET environment variable is not set");
 
   else if (access (QNX_TARGET, 0) != 0)
 
      DIE_WITH_EXITCODE (1, "the QNX_TARGET environment variable doesn't point to an existing directory");
 
 
 
   // open build file
 
   fopen_s (&buildfile_fp, buildfile_pathname, "rb");
 
   if (buildfile_fp == NULL)
 
      DIE_WITH_EXITCODE 
(1, "unable to open build file \"%s\" for reading: %s", buildfile_pathname
, strerror (errno
)); 
 
 
   // stack up filesystem entries
 
   memcpy (&entry_parms
, &default_parms
, sizeof (default_parms
));  
   entry_parms.st_mode = S_IFDIR | default_parms.dperms;
 
   add_fsentry (&fsentries, &fsentry_count, &entry_parms, "", NULL); // add the root dir first
 
 
 
   // parse -l arguments before everything else
 
   for (arg_index = 1; arg_index < argc; arg_index++)
 
      if ((strcmp (argv
[arg_index
], "-l") == 0) && (arg_index 
+ 1 < argc
))  
         parse_line (NULL, argv[++arg_index], &fsentries, &fsentry_count, &default_parms);
 
 
 
   // parse the IFS build file line per line
 
   while (fgets (line_buffer
, sizeof (line_buffer
), buildfile_fp
) != NULL
)  
   {
 
      if (current_line != NULL)
 
      current_line = strdup (line_buffer);
 
      ASSERT_WITH_ERRNO (current_line);
 
      lineno++; // keep track of current line number
 
      parse_line (buildfile_fp, line_buffer, &fsentries, &fsentry_count, &default_parms);
 
   }
 
 
 
   fclose (buildfile_fp
); // finished parsing the build file  
 
 
   // parse the IFS build file line per line
 
   while (fgets (line_buffer
, sizeof (line_buffer
), buildfile_fp
) != NULL
)  
   {
 
      if (current_line != NULL)
 
      current_line = strdup (line_buffer);
 
      ASSERT_WITH_ERRNO (current_line);
 
      lineno++; // keep track of current line number
 
 
 
   }
 
 
 
   fclose (buildfile_fp
); // finished parsing the build file  
 
 
   //////////////////////////////////
 
   // start constructing the IFS file
 
 
 
   Buffer_Initialize (&ifs.data);
 
 
 
   // do we have a startup file ? if so, this is a bootable image
 
   if (startupfile_pathname != NULL)
 
   {
 
      // write boot prefix
 
      // ######################################################################################################################################################################################################################################
 
      // # FIXME: figure out how to re-create it
 
      // ######################################################################################################################################################################################################################################
 
      buffer_t file;
 
      if (!Buffer_ReadFromFile (&file, bootfile_pathname))
 
         DIE_WITH_EXITCODE 
(1, "failed to open \"%s\" for reading: %s", bootfile_pathname
, strerror (errno
)); 
      ASSERT_WITH_ERRNO (Buffer_AppendBuffer (&ifs.data, &file)); // write boot blob
 
      Buffer_Forget (&file);
 
      ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (&ifs.data, ROUND_TO_UPPER_MULTIPLE (ifs.data.size, image_align))); // pad as necessary
 
 
 
      ifs.offsets.startupheader = ifs.data.size; // save startup header offset for future use
 
      memset (&startup_header
, 0, sizeof (startup_header
)); // prepare startup header  
      memcpy (startup_header.
signature, "\xeb\x7e\xff\x00", 4); // startup header signature, i.e. 0xff7eeb  
      startup_header.version       = 1;
 
      startup_header.flags1        = STARTUP_HDR_FLAGS1_VIRTUAL | STARTUP_HDR_FLAGS1_TRAILER_V2; // flags, 0x21 (STARTUP_HDR_FLAGS1_VIRTUAL | STARTUP_HDR_FLAGS1_TRAILER_V2)
 
      startup_header.header_size   = sizeof (startup_header); // 256
 
      if (strcmp (image_processor
, "x86_64") == 0)  
         startup_header.machine = ELF_MACHINE_X86_64; // EM_X86_64
 
      else if (strcmp (image_processor
, "aarch64le") == 0)  
         startup_header.machine = ELF_MACHINE_AARCH64; // EM_AARCH64
 
      else
 
         DIE_WITH_EXITCODE (1, "unsupported processor type '%s' found in build file \"%s\"", image_processor, buildfile_pathname); // should not happen
 
      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.*")
 
      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)
 
      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)
 
      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)
 
      startup_header.startup_size  = WILL_BE_FILLED_LATER;                                  // [I ] Size of startup (never compressed), here 0x02f148 or 192 840 bytes
 
      startup_header.stored_size   = WILL_BE_FILLED_LATER;                                  // [I ] Size of entire image, here 0x00cd6128 (same as ram_size)
 
      startup_header.imagefs_size  = WILL_BE_FILLED_LATER;                                  // [ S] Size of uncompressed imagefs, here 0x00ca6fe0 or 13 266 912 bytes
 
      startup_header.preboot_size  = (uint16_t) bootfile_size;                              // [I ] Size of loaded before header, here 0xf30 or 3888 bytes (size of "bios.boot" file))
 
      ASSERT_WITH_ERRNO (Buffer_Append (&ifs.data, &startup_header, sizeof (startup_header))); // write startup header
 
      ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (&ifs.data, ROUND_TO_UPPER_MULTIPLE (ifs.data.size, image_align))); // pad as necessary
 
 
 
      // ######################################################################################################################################################################################################################################
 
      // # FIXME: figure out how to re-create it:
 
      // first: open "startup-x86" ELF file,
 
      //        lookup section headers table (there is no program headers table in this one)
 
      //        FIXME: figure out something in there where the result is 0x1401030 !!!
 
      // 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
 
      // then: parse resulting ELF file, take all program segments and concatenate them --> this is the blob (FIXME: wrong?)
 
      // ######################################################################################################################################################################################################################################
 
#if 0 // nonworking
 
      // <deleted>
 
#else // working
 
      if (!Buffer_ReadFromFile (&file, startupfile_pathname))
 
         DIE_WITH_EXITCODE 
(1, "failed to open \"%s\" for reading: %s", startupfile_pathname
, strerror (errno
)); 
      ASSERT_WITH_ERRNO (Buffer_AppendBuffer (&ifs.data, &file)); // write startup blob
 
      Buffer_Forget (&file);
 
#endif // working
 
      ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (&ifs.data, ROUND_TO_UPPER_MULTIPLE (ifs.data.size, image_align))); // pad as necessary
 
 
 
      ifs.offsets.startuptrailer = ifs.data.size; // save startup trailer offset for future use
 
      ASSERT_WITH_ERRNO (Buffer_Append (&ifs.data, &startup_trailer, sizeof (startup_trailer))); // write startup trailer
 
      ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (&ifs.data, ROUND_TO_UPPER_MULTIPLE (ifs.data.size, image_align))); // pad as necessary
 
   }
 
 
 
   ifs.offsets.imageheader = ifs.data.size; // save image header offset for future use
 
   memset (&image_header
, 0, sizeof (image_header
)); // prepare image header  
   memcpy (&image_header.
signature, "imagefs", 7); // image filesystem signature, i.e. "imagefs"  
   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)
 
   image_header.image_size    = WILL_BE_FILLED_LATER; // size from header to end of trailer (here 0xca6fe0 or 13 266 912)
 
   image_header.hdr_dir_size  = WILL_BE_FILLED_LATER; // size from header to last dirent (here 0x12b8 or 4792)
 
   image_header.dir_offset    = sizeof (image_header); // offset from header to first dirent (here 0x5c or 92)
 
   image_header.boot_ino[0]   = image_kernel_ino; // inode of files for bootstrap p[ro?]g[ra?]ms (here 0xa0000002, 0, 0, 0)
 
   image_header.script_ino    = image_bootscript_ino; // inode of file for script (here 3)
 
   image_header.mountpoint[0] = '/'; // default mountpoint for image ("/" + "\0\0\0")
 
   ASSERT_WITH_ERRNO (Buffer_Append (&ifs.data, &image_header, sizeof (image_header))); // write image header
 
   ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (&ifs.data, ROUND_TO_UPPER_MULTIPLE (ifs.data.size, image_align))); // pad as necessary
 
 
 
   // write image directory (with the wrong file offsets)
 
   ifs.offsets.imagedir = ifs.data.size; // save image directory offset for future use
 
   curr_offset = ifs.offsets.imagedir;
 
   for (fsentry_index = 0; fsentry_index < fsentry_count; fsentry_index++)
 
   {
 
      Buffer_WriteIFSDirectoryEntryAt (&ifs.data, curr_offset, &fsentries[fsentry_index]); // write each dirent (the unknown fields will be fixed later)
 
      curr_offset += fsentries[fsentry_index].header.size; // advance to the next one
 
   }
 
   ASSERT_WITH_ERRNO (Buffer_AppendByteArray (&ifs.data, "\0\0\0\0")); // there seems to be 4 bytes of padding after the image directory
 
   imgdir_size = ifs.data.size - ifs.offsets.imagedir; // measure image dir size and save it for future use
 
 
 
   // is it a bootable image with a kernel file ?
 
   if ((startupfile_pathname != NULL) && (kernelfile_pathname != NULL))
 
   {
 
      // start by writing the startup script data blob, if we have one
 
      for (fsentry_index = 1; fsentry_index < fsentry_count; fsentry_index++)
 
         if (fsentries[fsentry_index].header.ino == image_bootscript_ino)
 
            break; // locate the startup script directory entry
 
      if (fsentry_index < fsentry_count) // found it ?
 
      {
 
         if (ifs.data.size + fsentries[fsentry_index].u.file.size >= kernelfile_offset)
 
            DIE_WITH_EXITCODE (1, "the compiled startup script is too big (%zd bytes, max is %zd) to fit at current offset %zd", (size_t) fsentries[fsentry_index].u.file.size, kernelfile_offset - ifs.data.size, ifs.data.size);
 
         fsentries[fsentry_index].u.file.offset = (uint32_t) (ifs.data.size - ifs.offsets.imageheader); // save file data blob offset in file structure
 
         Buffer_AppendIFSFileData (&ifs.data, &fsentries[fsentry_index]); // write file data
 
         fsentries[fsentry_index].UNSAVED_was_data_written = true; // and remember this file's data was written
 
      }
 
 
 
      // now write the filesystem entries that may fit before the kernel
 
      for (;;)
 
      {
 
         available_space = kernelfile_offset - ifs.data.size; // measure the available space until the kernel
 
 
 
         // look for the biggest one that can fit
 
         largest_index = 0;
 
         largest_size = 0;
 
         for (fsentry_index = 1; fsentry_index < fsentry_count; fsentry_index++)
 
         {
 
            if (!S_ISREG (fsentries[fsentry_index].header.mode) || fsentries[fsentry_index].UNSAVED_was_data_written || (fsentries[fsentry_index].u.file.size > available_space))
 
               continue; // skip all entries that don't have a separate data block, those who were written already and those that wouldn't fit
 
            if (fsentries[fsentry_index].u.file.size > largest_size)
 
            {
 
               largest_size = fsentries[fsentry_index].u.file.size;
 
               largest_index = fsentry_index;
 
            }
 
         }
 
         if (largest_size == 0)
 
            break; // found none ? if so, stop searching
 
         fsentry_index = largest_index;
 
 
 
         fsentries[fsentry_index].u.file.offset = (uint32_t) (ifs.data.size - ifs.offsets.imageheader); // save file data blob offset in file structure
 
         Buffer_AppendIFSFileData (&ifs.data, &fsentries[fsentry_index]); // write file data
 
         fsentries[fsentry_index].UNSAVED_was_data_written = true; // and remember this file's data was written
 
      }
 
      LOG_INFO ("Last written offset: 0x%zx", ifs.data.size);
 
      LOG_INFO ("Kernel file offset: 0x%zx", kernelfile_offset);
 
      ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (&ifs.data, kernelfile_offset)); // reach the kernel offset
 
 
 
      // now write the QNX kernel
 
      for (fsentry_index = 1; fsentry_index < fsentry_count; fsentry_index++)
 
         if (fsentries[fsentry_index].header.ino == image_kernel_ino)
 
            break; // locate the kernel directory entry (can't fail)
 
      fsentries[fsentry_index].u.file.offset = (uint32_t) (ifs.data.size - ifs.offsets.imageheader); // save file data blob offset in file structure
 
#ifdef PROCNTO_WIP
 
      // is the kernel we're storing a preprocessed ELF kernel ?
 
      if (fsentries[fsentry_index].header.ino & IFS_INO_PROCESSED_ELF)
 
      {
 
         elf = (elf_header_t *) fsentries[fsentry_index].u.file.UNSAVED_databuf; // quick access to ELF header
 
         table_count = ELF_GET_NUMERIC (elf, elf, program_header_table_len); // get the number of program headers
 
         for (table_index = 0; table_index < table_count; table_index++)
 
         {
 
            phdr = (elf_program_header_t *) &fsentries[fsentry_index].u.file.UNSAVED_databuf[ELF_GET_NUMERIC (elf, elf, program_header_table_offset) + (size_t) ELF_GET_NUMERIC (elf, elf, program_header_item_size) * table_index]; // quick access to program header
 
            corrective_offset = ELF_GET_NUMERIC (elf, phdr, virtual_addr) - ELF_GET_NUMERIC (elf, phdr, file_offset);
 
            if (ELF_GET_NUMERIC (elf, phdr, size_in_memory) != 0) // only patch the physical address of segments that have an actual size in memory
 
               ELF_SET_NUMERIC (elf, phdr, physical_addr, ELF_GET_NUMERIC (elf, phdr, physical_addr) + image_base + ifs.data.size - corrective_offset); // patch the physical address member of the program header table (NOTE: ifs.data.size is the location where the file data is about to be written)
 
         }
 
      }
 
#endif // PROCNTO_WIP
 
      Buffer_AppendIFSFileData (&ifs.data, &fsentries[fsentry_index]); // write kernel file data
 
      fsentries[fsentry_index].UNSAVED_was_data_written = true; // and remember this file's data was written
 
   }
 
 
 
   // then write all the other files by increasing inode number: ELF files first
 
   for (fsentry_index = 1; fsentry_index < fsentry_count; fsentry_index++)
 
   {
 
      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
 
          || (fsentries
[fsentry_index
].
u.
file.
size < 4) || (memcmp (fsentries
[fsentry_index
].
u.
file.
UNSAVED_databuf, ELF_MAGIC_STR
, 4) != 0)) // filter out anything that's not an ELF file  
         continue; // skip all entries that don't have a separate data block and those who were written already
 
      fsentries[fsentry_index].u.file.offset = (uint32_t) (ifs.data.size - ifs.offsets.imageheader); // save file data blob offset in file structure
 
      Buffer_AppendIFSFileData (&ifs.data, &fsentries[fsentry_index]); // write file data
 
      fsentries[fsentry_index].UNSAVED_was_data_written = true; // and remember this file's data was written
 
   }
 
   for (fsentry_index = 1; fsentry_index < fsentry_count; fsentry_index++) // other files (non-ELF, e.g. scripts and data files) last
 
   {
 
      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
 
         continue; // skip all entries that don't have a separate data block and those who were written already
 
      fsentries[fsentry_index].u.file.offset = (uint32_t) (ifs.data.size - ifs.offsets.imageheader); // save file data blob offset in file structure
 
      Buffer_AppendIFSFileData (&ifs.data, &fsentries[fsentry_index]); // write file data
 
      fsentries[fsentry_index].UNSAVED_was_data_written = true; // and remember this file's data was written
 
   }
 
   ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (&ifs.data, ROUND_TO_UPPER_MULTIPLE (ifs.data.size, image_align))); // pad as necessary
 
 
 
   // finally, write trailer (including empty checksum)
 
   ifs.offsets.imagetrailer = ifs.data.size; // save image trailer offset for future use
 
   ASSERT_WITH_ERRNO (Buffer_Append (&ifs.data, &image_trailer, sizeof (image_trailer))); // write image trailer
 
   ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (&ifs.data, ROUND_TO_UPPER_MULTIPLE (ifs.data.size, image_align))); // pad as necessary
 
 
 
   // if we need to pad it to a specific length, do so
 
   ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (&ifs.data, image_totalsize));
 
   ifs.final_size = ifs.data.size; // and this is the final size of the IFS
 
 
 
   // see if we are past the image max size, in which case it's an error
 
   if (ifs.final_size > image_maxsize)
 
      DIE_WITH_EXITCODE (1, "image file \"%s\" size %zd exceeds max size (%zd)", ifs_pathname, ifs.final_size, (size_t) image_maxsize);
 
 
 
   // do we have a startup file ? if so, this is a bootable image
 
   if (startupfile_pathname != NULL)
 
   {
 
      // patch the startup header with its final values
 
      startup_header.startup_size = (uint32_t) (ifs.offsets.imageheader - ifs.offsets.startupheader); // size of startup header up to image header
 
      startup_header.imagefs_size = (uint32_t) (ifs.final_size - ifs.offsets.imageheader); // size of uncompressed imagefs
 
      startup_header.ram_size     = (uint32_t) (ifs.final_size - ifs.offsets.startupheader);
 
      startup_header.stored_size  = (uint32_t) (ifs.final_size - ifs.offsets.startupheader);
 
      ASSERT_WITH_ERRNO (Buffer_WriteAt (&ifs.data, ifs.offsets.startupheader, &startup_header, sizeof (startup_header))); // write the final startup header at its right offset
 
   }
 
 
 
   // rewrite image header with final values
 
   image_header.image_size = (uint32_t) (ifs.final_size - ifs.offsets.imageheader); // size of uncompressed imagefs
 
   image_header.hdr_dir_size = sizeof (image_header) + (uint32_t) imgdir_size; // size from start of image header to last dirent
 
   ASSERT_WITH_ERRNO (Buffer_WriteAt (&ifs.data, ifs.offsets.imageheader, &image_header, sizeof (image_header))); // write image header
 
 
 
   // rewrite image directory with final offset values
 
   if (image_header.flags & IMAGE_FLAGS_SORTED)
 
      qsort (&fsentries
[1], fsentry_count 
- 1, sizeof (fsentry_t
), fsentry_compare_pathnames_cb
); // sort the filesystem entries by pathname if necessary  
   curr_offset = ifs.offsets.imagedir; // position ourselves at the beginning of the image directory
 
   for (fsentry_index = 0; fsentry_index < fsentry_count; fsentry_index++)
 
   {
 
      Buffer_WriteIFSDirectoryEntryAt (&ifs.data, curr_offset, &fsentries[fsentry_index]); // rewrite each dirent
 
      curr_offset += fsentries[fsentry_index].header.size; // advance to the next one
 
   }
 
 
 
   // ALL CHECKSUMS AT THE VERY END
 
 
 
   // do we have a startup file ? if so, this is a bootable image
 
   if (startupfile_pathname != NULL)
 
   {
 
      // compute SHA-512 checksum and V1 checksum of startup block
 
      if (   ( (startup_header.flags1 & STARTUP_HDR_FLAGS1_BIGENDIAN) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
 
          || (!(startup_header.flags1 & STARTUP_HDR_FLAGS1_BIGENDIAN) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)))
 
         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
 
      else
 
         is_foreign_endianness = false; // else this header is for the same endianness as us
 
 
 
      if (startup_header.flags1 & STARTUP_HDR_FLAGS1_TRAILER_V2) // is it a V2 trailer ?
 
      {
 
         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
 
         checksum = update_checksum (&ifs.data.bytes[ifs.offsets.startupheader], ifs.offsets.startuptrailer + SHA512_DIGEST_LENGTH - ifs.offsets.startupheader, is_foreign_endianness); // compute old checksum
 
         memcpy (&ifs.
data.
bytes[ifs.
offsets.
startuptrailer + SHA512_DIGEST_LENGTH
], &checksum
, 4); // and write it in place  
      }
 
      else // old V1 trailer
 
      {
 
         checksum = update_checksum (&ifs.data.bytes[ifs.offsets.startupheader], ifs.offsets.startuptrailer - ifs.offsets.startupheader, is_foreign_endianness); // compute old checksum
 
         memcpy (&ifs.
data.
bytes[ifs.
offsets.
startuptrailer], &checksum
, 4); // and write it in place  
      }
 
   }
 
 
 
   // compute SHA-512 checksum and V1 checksum of image block
 
   if (   ( (image_header.flags & IMAGE_FLAGS_BIGENDIAN) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
 
       || (!(image_header.flags & IMAGE_FLAGS_BIGENDIAN) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)))
 
      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
 
   else
 
      is_foreign_endianness = false; // else this header is for the same endianness as us
 
 
 
   if (image_header.flags & IMAGE_FLAGS_TRAILER_V2) // is it a V2 trailer ?
 
   {
 
      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
 
      checksum = update_checksum (&ifs.data.bytes[ifs.offsets.imageheader], ifs.offsets.imagetrailer + SHA512_DIGEST_LENGTH - ifs.offsets.imageheader, is_foreign_endianness); // compute old checksum
 
      memcpy (&ifs.
data.
bytes[ifs.
offsets.
imagetrailer + SHA512_DIGEST_LENGTH
], &checksum
, 4); // and write it in place  
   }
 
   else // old V1 trailer
 
   {
 
      checksum = update_checksum (&ifs.data.bytes[ifs.offsets.imageheader], ifs.offsets.imagetrailer - ifs.offsets.imageheader, is_foreign_endianness); // compute old checksum
 
      memcpy (&ifs.
data.
bytes[ifs.
offsets.
imagetrailer], &checksum
, 4); // and write it in place  
   }
 
 
 
   // now rewrite IFS with the correct checksums
 
   ASSERT_WITH_ERRNO (Buffer_WriteToFile (&ifs.data, ifs_pathname));
 
 
 
   // finished, cleanup
 
   for (fsentry_index = 0; fsentry_index < fsentry_count; fsentry_index++)
 
   {
 
   }
 
 
 
   // and exit with a success code
 
   LOG_INFO ("Success");
 
}