// ifstool.c -- portable reimplementation of QNX's mkifs by Pierre-Marie Baty <pm@pmbaty.com>
 
 
 
#include <stdint.h>
 
#include <stdbool.h>
 
#include <stdlib.h>
 
#include <stdarg.h>
 
#include <stdio.h>
 
#include <string.h>
 
#include <errno.h>
 
#include <sys/stat.h>
 
#include <ctype.h>
 
#include <time.h>
 
 
 
 
 
#ifdef _MSC_VER
 
#include <io.h>
 
#define __ORDER_BIG_ENDIAN__    4321
 
#define __ORDER_LITTLE_ENDIAN__ 1234
 
#define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__
 
#define __attribute__(x)
 
#define __builtin_bswap32(x) _byteswap_ulong ((unsigned long) (x))
 
#define __builtin_bswap64(x) _byteswap_uint64 ((unsigned long long) (x))
 
#define S_IFIFO 0x1000
 
#define S_IFLNK 0xa000
 
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
 
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
 
#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
 
#define strdup(s) _strdup ((s))
 
#define strcasecmp(s1,s2) _stricmp ((s1), (s2))
 
#define fseek(fp,off,m) _fseeki64 ((fp), (off), (m))
 
#define access(p,m) _access ((p), (m))
 
#define MAXPATHLEN 1024
 
#else // !_MSC_VER
 
#include <sys/param.h>
 
#include <unistd.h>
 
#endif // _MSC_VER
 
 
 
#ifdef _MSC_VER
 
#pragma pack(push)
 
#pragma pack(1)
 
#endif // _MSC_VER
 
 
 
 
 
// handy macros that generate a version number in the format "YYYYMMDD" corresponding to the build date. Usage: printf ("version " VERSION_FMT_YYYYMMDD "\n", VERSION_ARG_YYYYMMDD);
 
#define BUILDDATE_YEAR  (&__DATE__[7]) // compiler will optimize this into a const string, e.g. "2021"
 
#define BUILDDATE_MONTH (*((uint32_t *) ((void *) __DATE__)) == *((uint32_t *) ((void *) "Jan ")) ? "01" : \
 
                         (*((uint32_t *) ((void *) __DATE__)) == *((uint32_t *) ((void *) "Feb ")) ? "02" : \
 
                          (*((uint32_t *) ((void *) __DATE__)) == *((uint32_t *) ((void *) "Mar ")) ? "03" : \
 
                           (*((uint32_t *) ((void *) __DATE__)) == *((uint32_t *) ((void *) "Apr ")) ? "04" : \
 
                            (*((uint32_t *) ((void *) __DATE__)) == *((uint32_t *) ((void *) "May ")) ? "05" : \
 
                             (*((uint32_t *) ((void *) __DATE__)) == *((uint32_t *) ((void *) "Jun ")) ? "06" : \
 
                              (*((uint32_t *) ((void *) __DATE__)) == *((uint32_t *) ((void *) "Jul ")) ? "07" : \
 
                               (*((uint32_t *) ((void *) __DATE__)) == *((uint32_t *) ((void *) "Aug ")) ? "08" : \
 
                                (*((uint32_t *) ((void *) __DATE__)) == *((uint32_t *) ((void *) "Sep ")) ? "09" : \
 
                                 (*((uint32_t *) ((void *) __DATE__)) == *((uint32_t *) ((void *) "Oct ")) ? "10" : \
 
                                  (*((uint32_t *) ((void *) __DATE__)) == *((uint32_t *) ((void *) "Nov ")) ? "11" : \
 
                                   (*((uint32_t *) ((void *) __DATE__)) == *((uint32_t *) ((void *) "Dec ")) ? "12" : "XX")))))))))))) // compiler will optimize this into a const string, e.g. "11"
 
#define BUILDDATE_DAY   (*((uint16_t *) ((void *) &__DATE__[4])) == *((uint16_t *) ((void *) " 1")) ? "01" : \
 
                         (*((uint16_t *) ((void *) &__DATE__[4])) == *((uint16_t *) ((void *) " 2")) ? "02" : \
 
                          (*((uint16_t *) ((void *) &__DATE__[4])) == *((uint16_t *) ((void *) " 3")) ? "03" : \
 
                           (*((uint16_t *) ((void *) &__DATE__[4])) == *((uint16_t *) ((void *) " 4")) ? "04" : \
 
                            (*((uint16_t *) ((void *) &__DATE__[4])) == *((uint16_t *) ((void *) " 5")) ? "05" : \
 
                             (*((uint16_t *) ((void *) &__DATE__[4])) == *((uint16_t *) ((void *) " 6")) ? "06" : \
 
                              (*((uint16_t *) ((void *) &__DATE__[4])) == *((uint16_t *) ((void *) " 7")) ? "07" : \
 
                               (*((uint16_t *) ((void *) &__DATE__[4])) == *((uint16_t *) ((void *) " 8")) ? "08" : \
 
                                (*((uint16_t *) ((void *) &__DATE__[4])) == *((uint16_t *) ((void *) " 9")) ? "09" : \
 
                                 (*((uint16_t *) ((void *) &__DATE__[4])) == *((uint16_t *) ((void *) "10")) ? "10" : \
 
                                  (*((uint16_t *) ((void *) &__DATE__[4])) == *((uint16_t *) ((void *) "11")) ? "11" : \
 
                                   (*((uint16_t *) ((void *) &__DATE__[4])) == *((uint16_t *) ((void *) "12")) ? "12" : \
 
                                    (*((uint16_t *) ((void *) &__DATE__[4])) == *((uint16_t *) ((void *) "13")) ? "13" : \
 
                                     (*((uint16_t *) ((void *) &__DATE__[4])) == *((uint16_t *) ((void *) "14")) ? "14" : \
 
                                      (*((uint16_t *) ((void *) &__DATE__[4])) == *((uint16_t *) ((void *) "15")) ? "15" : \
 
                                       (*((uint16_t *) ((void *) &__DATE__[4])) == *((uint16_t *) ((void *) "16")) ? "16" : \
 
                                        (*((uint16_t *) ((void *) &__DATE__[4])) == *((uint16_t *) ((void *) "17")) ? "17" : \
 
                                         (*((uint16_t *) ((void *) &__DATE__[4])) == *((uint16_t *) ((void *) "18")) ? "18" : \
 
                                          (*((uint16_t *) ((void *) &__DATE__[4])) == *((uint16_t *) ((void *) "19")) ? "19" : \
 
                                           (*((uint16_t *) ((void *) &__DATE__[4])) == *((uint16_t *) ((void *) "20")) ? "20" : \
 
                                            (*((uint16_t *) ((void *) &__DATE__[4])) == *((uint16_t *) ((void *) "21")) ? "21" : \
 
                                             (*((uint16_t *) ((void *) &__DATE__[4])) == *((uint16_t *) ((void *) "22")) ? "22" : \
 
                                              (*((uint16_t *) ((void *) &__DATE__[4])) == *((uint16_t *) ((void *) "23")) ? "23" : \
 
                                               (*((uint16_t *) ((void *) &__DATE__[4])) == *((uint16_t *) ((void *) "24")) ? "24" : \
 
                                                (*((uint16_t *) ((void *) &__DATE__[4])) == *((uint16_t *) ((void *) "25")) ? "25" : \
 
                                                 (*((uint16_t *) ((void *) &__DATE__[4])) == *((uint16_t *) ((void *) "26")) ? "26" : \
 
                                                  (*((uint16_t *) ((void *) &__DATE__[4])) == *((uint16_t *) ((void *) "27")) ? "27" : \
 
                                                   (*((uint16_t *) ((void *) &__DATE__[4])) == *((uint16_t *) ((void *) "28")) ? "28" : \
 
                                                    (*((uint16_t *) ((void *) &__DATE__[4])) == *((uint16_t *) ((void *) "29")) ? "29" : \
 
                                                     (*((uint16_t *) ((void *) &__DATE__[4])) == *((uint16_t *) ((void *) "30")) ? "30" : \
 
                                                      (*((uint16_t *) ((void *) &__DATE__[4])) == *((uint16_t *) ((void *) "31")) ? "31" : "XX"))))))))))))))))))))))))))))))) // compiler will optimize this into a const string, e.g. "14"
 
#define VERSION_FMT_YYYYMMDD "%04s%02s%02s"
 
#define VERSION_ARG_YYYYMMDD BUILDDATE_YEAR, BUILDDATE_MONTH, BUILDDATE_DAY
 
 
 
 
 
#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) == '\\'))
 
#define PATH_SEP ';'
 
#else // !_WIN32, thus POSIX
 
#define IS_DIRSEP(c) ((c) == '/')
 
#define PATH_SEP ':'
 
#endif // _WIN32
 
#define RECORD_SEP '\x1e' // ASCII record separator
 
#define RECORD_SEP_STR "\x1e" // ASCII record separator (as string)
 
 
 
#define WILL_BE_FILLED_LATER 0xbaadf00d
 
 
 
 
 
// bitmapped flags used in the flags1 member of the startup header
 
#define STARTUP_HDR_FLAGS1_VIRTUAL        (1 << 0)
 
#define STARTUP_HDR_FLAGS1_BIGENDIAN      (1 << 1)
 
//#define STARTUP_HDR_FLAGS1_COMPRESS_MASK  0x1c
 
//#define STARTUP_HDR_FLAGS1_COMPRESS_SHIFT 0x02
 
//#define STARTUP_HDR_FLAGS1_COMPRESS_NONE  0x00
 
//#define STARTUP_HDR_FLAGS1_COMPRESS_ZLIB  0x04
 
//#define STARTUP_HDR_FLAGS1_COMPRESS_LZO   0x08
 
//#define STARTUP_HDR_FLAGS1_COMPRESS_UCL   0x0c
 
#define STARTUP_HDR_FLAGS1_TRAILER_V2     (1 << 5) // if set, then a struct startup_trailer_v2 follows the startup. If the image is compressed, then the compressed imagefs is followed by a struct image_trailer_v2
 
 
 
 
 
#define STARTUP_HDR_MACHINE_X86_64  0x3e
 
#define STARTUP_HDR_MACHINE_AARCH64 0xb7
 
 
 
 
 
// bitmapped flags used in the flags member of the image header
 
#define IMAGE_FLAGS_BIGENDIAN  (1 << 0) // header, trailer, dirents in big-endian format
 
#define IMAGE_FLAGS_READONLY   (1 << 1) // do not try to write to image (rom/flash)
 
#define IMAGE_FLAGS_INO_BITS   (1 << 2) // inode bits valid
 
#define IMAGE_FLAGS_SORTED     (1 << 3) // dirent section is sorted (by pathname)
 
#define IMAGE_FLAGS_TRAILER_V2 (1 << 4) // image uses struct image_trailer_v2
 
 
 
 
 
// bitmapped flags superposed to a filesystem entry's inode number
 
#define IFS_INO_PROCESSED_ELF 0x80000000
 
#define IFS_INO_RUNONCE_ELF   0x40000000
 
#define IFS_INO_BOOTSTRAP_EXE 0x20000000
 
 
 
 
 
// SHA-512 block and digest sizes
 
#define SHA512_BLOCK_LENGTH 128 // in bytes
 
#define SHA512_DIGEST_LENGTH 64 // in bytes
 
 
 
 
 
// SHA-512 computation context structure type definition
 
typedef struct sha512_ctx_s
 
{
 
   uint64_t state[8];
 
   uint64_t bitcount[2];
 
   uint8_t buffer[SHA512_BLOCK_LENGTH];
 
} SHA512_CTX;
 
 
 
 
 
#if 0 // TODO: startup script compiler. Someday.
 
#define SCRIPT_FLAGS_EXTSCHED   0x01
 
#define SCRIPT_FLAGS_SESSION    0x02
 
#define SCRIPT_FLAGS_SCHED_SET  0x04
 
#define SCRIPT_FLAGS_CPU_SET    0x08
 
#define SCRIPT_FLAGS_BACKGROUND 0x20
 
#define SCRIPT_FLAGS_KDEBUG     0x40
 
 
 
#define SCRIPT_POLICY_NOCHANGE 0
 
#define SCRIPT_POLICY_FIFO     1
 
#define SCRIPT_POLICY_RR       2
 
#define SCRIPT_POLICY_OTHER    3
 
 
 
#define SCRIPT_TYPE_EXTERNAL        0
 
#define SCRIPT_TYPE_WAITFOR         1
 
#define SCRIPT_TYPE_REOPEN          2
 
#define SCRIPT_TYPE_DISPLAY_MSG     3
 
#define SCRIPT_TYPE_PROCMGR_SYMLINK 4
 
#define SCRIPT_TYPE_EXTSCHED_APS    5
 
 
 
#define SCRIPT_CHECKS_MS 100
 
 
 
#define SCRIPT_SCHED_EXT_NONE 0
 
#define SCRIPT_SCHED_EXT_APS  1
 
 
 
#define SCRIPT_APS_SYSTEM_PARTITION_ID   0
 
#define SCRIPT_APS_SYSTEM_PARTITION_NAME "System"
 
#define SCRIPT_APS_PARTITION_NAME_LENGTH 15
 
#define SCRIPT_APS_MAX_PARTITIONS        8
 
 
 
 
 
typedef struct __attribute__((packed)) bootscriptcmd_header_s
 
{
 
   uint16_t size; // size of cmd entry
 
   uint8_t type;
 
   uint8_t spare;
 
} bootscriptcmd_header_t;
 
 
 
 
 
typedef union bootscriptcmd_s
 
{
 
   struct __attribute__((packed)) script_external
 
   {
 
      bootscriptcmd_header_t hdr;
 
      uint8_t cpu; // CPU (turn into runmask)
 
      uint8_t flags;
 
      union script_external_extsched
 
      {
 
         uint8_t reserved[2];
 
         struct __attribute__((packed))
 
         {
 
            uint8_t id;
 
            uint8_t reserved[1];
 
         } aps;
 
      } extsched; // extended scheduler
 
      uint8_t policy; // POLICY_FIFO, POLICY_RR, ...
 
      uint8_t priority; // priority to run cmd at
 
      uint8_t argc; // # of args
 
      uint8_t envc; // # of environment entries
 
      char args[0]; // executable, argv, envp (null padded to 32-bit align)
 
   } external;
 
   struct __attribute__((packed)) script_waitfor_reopen
 
   {
 
      bootscriptcmd_header_t hdr;
 
      uint16_t checks;
 
      char fname[0]; // char fname[] (null padded to 32-bit align)
 
   } waitfor_reopen;
 
   struct __attribute__((packed)) script_display_msg
 
   {
 
      bootscriptcmd_header_t hdr;
 
      char msg[0]; // char msg[] (null padded to 32-bit align)
 
   } display_msg;
 
   struct __attribute__((packed)) script_procmgr_symlink
 
   {
 
      bootscriptcmd_header_t hdr;
 
      char src_dest[0]; // <src_name>, '\0', <dest_name> '\0' (null padded to 32-bit align)
 
   } procmgr_symlink;
 
   struct __attribute__((packed)) script_extsched_aps
 
   {
 
      bootscriptcmd_header_t hdr;
 
      uint8_t parent;
 
      uint8_t budget;
 
      uint16_t critical;
 
      uint8_t id;
 
      char pname[0]; // char pname[] (null padded to 32-bit align)
 
   } extsched_aps;
 
} bootscriptcmd_t;
 
#endif // 0
 
 
 
 
 
#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\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"
 
 
 
 
 
typedef struct __attribute__((packed)) fsentry_s
 
{
 
   struct __attribute__((packed)) fsentry_header_s
 
   {
 
      uint16_t size; // size of dirent
 
      uint16_t extattr_offset; // if zero, no extattr data
 
      uint32_t ino; // if zero, skip entry
 
      uint32_t mode; // mode and perms of entry
 
      uint32_t gid;
 
      uint32_t uid;
 
      uint32_t mtime;
 
   } header;
 
   union __attribute__((packed)) fsentry_specific_u
 
   {
 
      struct __attribute__((packed)) fsentry_file_s // when (mode & S_IFMT) == S_IFREG
 
      {
 
         uint32_t offset; // offset from header
 
         uint32_t size;
 
         char *path; // null terminated path (no leading slash)
 
         char *UNSAVED_databuf; // file data blob buffer (NOT SAVED IN THE IFS)
 
      } file;
 
      struct __attribute__((packed)) fsentry_dir_s // when (mode & S_IFMT) == S_IFDIR
 
      {
 
         char *path; // null terminated path (no leading slash)
 
      } dir;
 
      struct __attribute__((packed)) fsentry_symlink_s // when (mode & S_IFMT) == S_IFLNK
 
      {
 
         uint16_t sym_offset; // offset to 'contents' from 'path'
 
         uint16_t sym_size; // strlen (contents)
 
         char *path; // null terminated path (no leading slash)
 
         char *contents; // null terminated symlink contents
 
      } symlink;
 
      struct __attribute__((packed)) fsentry_device_s // when (mode & S_IFMT) == S_IF<CHR|BLK|FIFO|NAM|SOCK>
 
      {
 
         uint32_t dev;
 
         uint32_t rdev;
 
         char *path; // null terminated path (no leading slash)
 
      } device;
 
   } u;
 
   bool UNSAVED_was_data_written; // whether this entry's data was written to the image (NOT SAVED IN THE IFS)
 
} fsentry_t;
 
 
 
 
 
typedef struct __attribute__((packed)) startup_header_s // size 256 bytes
 
{
 
                           // I - used by the QNX IPL
 
                           // S - used by the startup program
 
   uint8_t signature[4];   // [I ] Header signature, "\xeb\x7e\xff\x00"
 
   uint16_t version;       // [I ] Header version, i.e. 1
 
   uint8_t flags1;         // [IS] Misc flags, 0x21 (= 0x20 | STARTUP_HDR_FLAGS1_VIRTUAL)
 
   uint8_t flags2;         // [  ] No flags defined yet (0)
 
   uint16_t header_size;   // [ S] sizeof(struct startup_header), i.e. 256
 
   uint16_t machine;       // [IS] Machine type from elfdefinitions.h, i.e. 0x003E --> _ELF_DEFINE_EM(EM_X86_64, 62, "AMD x86-64 architecture")
 
   uint32_t startup_vaddr; // [I ] Virtual Address to transfer to after IPL is done, here 0x01403008 (appears in "Entry" column for "startup.*")
 
   uint32_t paddr_bias;    // [ S] Value to add to physical address to get a value to put into a pointer and indirected through, here 0 (no indirections)
 
   uint32_t image_paddr;   // [IS] Physical address of image, here 0x01400f30 (appears in "Offset" column for "startup-header" which is the first entry/start of file)
 
   uint32_t ram_paddr;     // [IS] Physical address of RAM to copy image to (startup_size bytes copied), here 0x01400f30 (same as above)
 
   uint32_t ram_size;      // [ 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
 
   uint32_t startup_size;  // [I ] Size of startup (never compressed), here 0x02f148 or 192 840 bytes
 
   uint32_t stored_size;   // [I ] Size of entire image, here 0x00cd6128 (same as ram_size)
 
   uint32_t imagefs_paddr; // [IS] Set by IPL to where the imagefs is when startup runs (0)
 
   uint32_t imagefs_size;  // [ S] Size of uncompressed imagefs, here 0x00ca6fe0 or 13 266 912 bytes
 
   uint16_t preboot_size;  // [I ] Size of loaded before header, here 0xf30 or 3888 bytes (size of "bios.boot" file))
 
   uint16_t zero0;         // [  ] Zeros
 
   uint32_t zero[1];       // [  ] Zeros
 
   uint64_t addr_off;      // [ S] Offset to add to startup_vaddr, image_paddr, ram_paddr, and imagefs_paddr members, here zero (0)
 
   uint32_t info[48];      // [IS] Array of startup_info* structures (zero filled)
 
} startup_header_t;
 
 
 
 
 
typedef struct __attribute__((packed)) startup_trailer_s
 
{
 
   uint32_t cksum; // checksum from start of header to start of trailer
 
} startup_trailer_v1_t;
 
 
 
 
 
// NOTE: The checksums in this trailer will only be valid prior to entering startup.
 
// Because the startup binary is executed in-place, its data segment will change once the program is running.
 
// Hence, any checksum validation would need to be done by the boot loader / IFS.
 
typedef struct __attribute__((packed)) startup_trailer_v2_s
 
{
 
   uint8_t sha512[64]; // SHA512 from start of header to start of trailer
 
   uint32_t cksum; // checksum from start of header to start of this member
 
} startup_trailer_v2_t;
 
 
 
 
 
typedef struct __attribute__((packed)) image_header_s
 
{
 
   uint8_t signature[7]; // image filesystem signature, i.e. "imagefs"
 
   uint8_t flags; // endian neutral flags, 0x1c
 
   uint32_t image_size; // size from start of header to end of trailer (here 0xca6fe0 or 13 266 912)
 
   uint32_t hdr_dir_size; // size from start of header to last dirent (here 0x12b8 or 4792)
 
   uint32_t dir_offset; // offset from start of header to start of first dirent (here 0x5c or 92)
 
   uint32_t boot_ino[4]; // inode of files for bootstrap pgms (here 0xa0000002, 0, 0, 0)
 
   uint32_t script_ino; // inode of file for script (here 3)
 
   uint32_t chain_paddr; // offset to next filesystem signature (0)
 
   uint32_t spare[10]; // zerofill
 
   uint32_t mountflags; // default _MOUNT_* from sys/iomsg.h (0)
 
   char mountpoint[4]; // default mountpoint for image ("/" + "\0\0\0")
 
} image_header_t;
 
 
 
 
 
typedef struct __attribute__((packed)) image_trailer_v1_s
 
{
 
   uint32_t cksum; // checksum from start of header to start of trailer
 
} image_trailer_v1_t; // NOTE: this is the same structure as startup_trailer_v1_t
 
 
 
 
 
// NOTE: the checksums in this trailer will only be valid until the first non-startup bootstrap binary (e.g., startup-verifier, procnto, ...) is invoked.
 
// Because bootstrap binaries execute in-place, their data segments will change once the programs are running.
 
// Hence, any checksum validation would need to be done either by the boot loader / IFS or by the startup.
 
typedef struct __attribute__((packed)) image_trailer_v2_s
 
{
 
   uint8_t sha512[64]; // SHA512 from start of image header to start of trailer
 
   uint32_t cksum; // checksum from start of header to start of this member
 
} image_trailer_v2_t; // NOTE: this is the same structure as startup_trailer_v2_t
 
 
 
 
 
#ifdef _MSC_VER
 
#pragma pack(pop)
 
#endif // _MSC_VER
 
 
 
 
 
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
 
   char prefix[MAXPATHLEN]; // install path (e.g. "proc/boot")
 
   bool is_compiled_bootscript; // entry has [+script] attribute
 
   char search[10 * MAXPATHLEN]; // binary search path (the default one will be constructed at startup)
 
 
 
   uint8_t *precompiled_data;
 
   size_t precompiled_datalen;
 
} parms_t;
 
 
 
 
 
// global variables
 
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;
 
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 *buildfile_pathname = NULL; // pathname of 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
 
 
 
 
 
// prototypes of local functions
 
static void sha512_private_transform (SHA512_CTX *context, const uint64_t *data); // used internally in SHA512_Update() and SHA512_Final()
 
static void SHA512_Init (SHA512_CTX *context);
 
static void SHA512_Update (SHA512_CTX *context, void *data, size_t len);
 
static void SHA512_Final (uint8_t digest[SHA512_DIGEST_LENGTH], SHA512_CTX *context);
 
static uint8_t *SHA512 (void *data, size_t data_len, uint8_t *digest); // computes a SHA-512 in one pass (shortcut for SHA512_Init(), SHA512_Update() N times and SHA512_Final())
 
static int32_t update_checksum (const int32_t start_value, 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
 
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 void hex_fprintf (FILE *fp, const uint8_t *data, size_t data_size, int howmany_columns, const char *fmt, ...); // hexdump-style formatted output to a file stream (which may be stdout/stderr)
 
static char *binary (const uint8_t x, char char_for_zero, char char_for_one); // returns the binary representation of byte 'x' as a string
 
static char *describe_uint8 (const uint8_t x, const char *bitwise_stringdescs[8]); // returns the ORed description of byte 'x' according to the description strings for each bit
 
static int fwrite_filecontents (const char *pathname, FILE *fp); // dumps the contents of pathname into fp
 
static size_t fwrite_fsentry (const fsentry_t *fsentry, FILE *fp); // writes the given filesystem entry into fp (without its contents)
 
static size_t add_fsentry (fsentry_t **fsentries, size_t *fsentry_count, const char *stored_pathname, parms_t *entry_parms, const uint8_t *data, const size_t entry_datalen); // 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 int fsentry_compare_sizes_cb (const void *a, const void *b); // qsort() comparison callback that sorts filesystem entries by size
 
static int dump_ifs_info (const char *ifs_pathname); // dumps detailed info about a particular IFS file on the standard output, returns 0 on success and >0 on error
 
 
 
 
 
static void sha512_private_transform (SHA512_CTX *context, const uint64_t *data)
 
{
 
   // logical functions used in SHA-384 and SHA-512
 
   #define S64(b,x)      (((x) >> (b)) | ((x) << (64 - (b)))) // 64-bit rotate right
 
   #define Ch(x,y,z)     (((x) & (y)) ^ ((~(x)) & (z)))
 
   #define Maj(x,y,z)    (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
 
   #define Sigma0_512(x) (S64(28, (x)) ^ S64(34, (x)) ^ S64(39, (x)))
 
   #define Sigma1_512(x) (S64(14, (x)) ^ S64(18, (x)) ^ S64(41, (x)))
 
   #define sigma0_512(x) (S64( 1, (x)) ^ S64( 8, (x)) ^ ((x) >> 7))
 
   #define sigma1_512(x) (S64(19, (x)) ^ S64(61, (x)) ^ ((x) >> 6))
 
 
 
   // hash constant words K for SHA-384 and SHA-512
 
   static const uint64_t K512[80] = {
 
      0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL, 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL, 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL,
 
      0xd807aa98a3030242ULL, 0x12835b0145706fbeULL, 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL, 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL,
 
      0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL, 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, 0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL,
 
      0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL, 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL,
 
      0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL, 0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL,
 
      0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, 0xd192e819d6ef5218ULL, 0xd69906245565a910ULL, 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL,
 
      0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL, 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL, 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL,
 
      0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL, 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL,
 
      0xca273eceea26619cULL, 0xd186b8c721c0c207ULL, 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, 0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, 0x113f9804bef90daeULL, 0x1b710b35131c471bULL,
 
      0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL, 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL
 
   };
 
 
 
   uint64_t     a, b, c, d, e, f, g, h, s0, s1;
 
   uint64_t     T1, T2, *W512 = (uint64_t *) context->buffer;
 
   int j;
 
 
 
   // initialize registers with the prev. intermediate value
 
   a = context->state[0]; b = context->state[1]; c = context->state[2]; d = context->state[3]; e = context->state[4]; f = context->state[5]; g = context->state[6]; h = context->state[7];
 
 
 
   for (j = 0; j < 16; j++)
 
   {
 
#if __BYTE_ORDER__ ==  __ORDER_LITTLE_ENDIAN__
 
      W512[j] = __builtin_bswap64 (*data); // convert to host byte order
 
#elif // __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
 
      W512[j] = *data;
 
#else // __BYTE_ORDER__ == ???
 
#error Please port this SHA-512 code to your exotic endianness platform. What are you compiling this on? PDP? Honeywell?
 
#endif // __BYTE_ORDER__ ==  __ORDER_LITTLE_ENDIAN__
 
 
 
      // apply the SHA-512 compression function to update a..h
 
      T1 = h + Sigma1_512 (e) + Ch (e, f, g) + K512[j] + W512[j];
 
      T2 = Sigma0_512 (a) + Maj (a, b, c);
 
 
 
      // update registers
 
      h = g; g = f; f = e; e = d + T1; d = c; c = b; b = a; a = T1 + T2;
 
 
 
      data++;
 
   }
 
 
 
   for (; j < 80; j++)
 
   {
 
      // part of the message block expansion
 
      s0 = W512[(j + 1) & 0x0f];
 
      s0 = sigma0_512 (s0);
 
      s1 = W512[(j + 14) & 0x0f];
 
      s1 = sigma1_512 (s1);
 
 
 
      // apply the SHA-512 compression function to update a..h
 
      T1 = h + Sigma1_512 (e) + Ch (e, f, g) + K512[j] + (W512[j & 0x0f] += s1 + W512[(j + 9) & 0x0f] + s0);
 
      T2 = Sigma0_512 (a) + Maj (a, b, c);
 
 
 
      // update registers
 
      h = g; g = f; f = e; e = d + T1; d = c; c = b; b = a; a = T1 + T2;
 
   }
 
 
 
   // compute the current intermediate hash value
 
   context->state[0] += a; context->state[1] += b; context->state[2] += c; context->state[3] += d; context->state[4] += e; context->state[5] += f; context->state[6] += g; context->state[7] += h;
 
 
 
   // clean up
 
   a = b = c = d = e = f = g = h = T1 = T2 = 0;
 
   #undef sigma1_512
 
   #undef sigma0_512
 
   #undef Sigma1_512
 
   #undef Sigma0_512
 
   #undef Maj
 
   #undef Ch
 
   #undef S64
 
   return;
 
}
 
 
 
 
 
static void SHA512_Init (SHA512_CTX *context)
 
{
 
   // initial hash value H for SHA-512
 
   static const uint64_t sha512_initial_hash_value[8] = {
 
      0x6a09e667f3bcc908ULL, 0xbb67ae8584caa73bULL, 0x3c6ef372fe94f82bULL, 0xa54ff53a5f1d36f1ULL, 0x510e527fade682d1ULL, 0x9b05688c2b3e6c1fULL, 0x1f83d9abfb41bd6bULL, 0x5be0cd19137e2179ULL
 
   };
 
 
 
   memcpy (context
->state
, sha512_initial_hash_value
, SHA512_DIGEST_LENGTH
);  
   memset (context
->buffer
, 0, SHA512_BLOCK_LENGTH
);  
   context->bitcount[0] = context->bitcount[1] = 0;
 
}
 
 
 
 
 
void SHA512_Update (SHA512_CTX *context, void *datain, size_t len)
 
{
 
   #define ADDINC128(w,n) do { \
 
           (w)[0] += (uint64_t) (n); \
 
           if ((w)[0] < (n)) \
 
                   (w)[1]++; \
 
   } while (0) // macro for incrementally adding the unsigned 64-bit integer n to the unsigned 128-bit integer (represented using a two-element array of 64-bit words
 
 
 
   size_t freespace, usedspace;
 
   const uint8_t *data = (const uint8_t *) datain;
 
 
 
   if (len == 0)
 
      return; // calling with empty data is valid - we do nothing
 
 
 
   usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH;
 
   if (usedspace > 0)
 
   {
 
      // calculate how much free space is available in the buffer
 
      freespace = SHA512_BLOCK_LENGTH - usedspace;
 
 
 
      if (len >= freespace)
 
      {
 
         // fill the buffer completely and process it
 
         memcpy (&context
->buffer
[usedspace
], data
, freespace
);  
         ADDINC128 (context->bitcount, freespace << 3);
 
         len -= freespace;
 
         data += freespace;
 
         sha512_private_transform (context, (uint64_t *) context->buffer);
 
      }
 
      else
 
      {
 
         // the buffer is not full yet
 
         memcpy (&context
->buffer
[usedspace
], data
, len
);  
         ADDINC128 (context->bitcount, len << 3);
 
 
 
         // clean up
 
         usedspace = freespace = 0;
 
         return;
 
      }
 
   }
 
 
 
   while (len >= SHA512_BLOCK_LENGTH)
 
   {
 
      // process as many complete blocks as we can
 
      sha512_private_transform (context, (uint64_t *) data);
 
      ADDINC128 (context->bitcount, SHA512_BLOCK_LENGTH << 3);
 
      len -= SHA512_BLOCK_LENGTH;
 
      data += SHA512_BLOCK_LENGTH;
 
   }
 
 
 
   if (len > 0)
 
   {
 
      // save leftovers
 
      memcpy (context
->buffer
, data
, len
);  
      ADDINC128 (context->bitcount, len << 3);
 
   }
 
 
 
   // clean up
 
   usedspace = freespace = 0;
 
   #undef ADDINC128
 
   return;
 
}
 
 
 
 
 
static void SHA512_Final (uint8_t digest[SHA512_DIGEST_LENGTH], SHA512_CTX *context)
 
{
 
   #define SHA512_SHORT_BLOCK_LENGTH (SHA512_BLOCK_LENGTH - 16)
 
 
 
   size_t usedspace;
 
   union { uint8_t *as_bytes; uint64_t *as_uint64s; } cast_var = { NULL };
 
 
 
   // if no digest buffer is passed, don't bother finalizing the computation
 
   if (digest != NULL)
 
   {
 
      usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH;
 
 
 
#if __BYTE_ORDER__ ==  __ORDER_LITTLE_ENDIAN__
 
      context->bitcount[0] = __builtin_bswap64 (context->bitcount[0]); // convert from host byte order
 
      context->bitcount[1] = __builtin_bswap64 (context->bitcount[1]); // convert from host byte order
 
#endif // __BYTE_ORDER__ ==  __ORDER_LITTLE_ENDIAN__
 
 
 
      if (usedspace > 0)
 
      {
 
         // begin padding with a 1 bit
 
         context->buffer[usedspace++] = 0x80;
 
 
 
         if (usedspace <= SHA512_SHORT_BLOCK_LENGTH)
 
            memset (&context
->buffer
[usedspace
], 0, SHA512_SHORT_BLOCK_LENGTH 
- usedspace
); // set-up for the last transform  
         else
 
         {
 
            if (usedspace < SHA512_BLOCK_LENGTH)
 
               memset (&context
->buffer
[usedspace
], 0, SHA512_BLOCK_LENGTH 
- usedspace
);  
 
 
            sha512_private_transform (context, (uint64_t *) context->buffer); // do second-to-last transform
 
            memset (context
->buffer
, 0, SHA512_BLOCK_LENGTH 
- 2); // and set-up for the last transform  
         }
 
      }
 
      else // usedspace == 0
 
      {
 
         memset (context
->buffer
, 0, SHA512_SHORT_BLOCK_LENGTH
); // prepare for final transform  
         *context->buffer = 0x80; // begin padding with a 1 bit
 
      }
 
 
 
      // store the length of input data (in bits)
 
      cast_var.as_bytes = context->buffer;
 
      cast_var.as_uint64s[SHA512_SHORT_BLOCK_LENGTH / 8 + 0] = context->bitcount[1];
 
      cast_var.as_uint64s[SHA512_SHORT_BLOCK_LENGTH / 8 + 1] = context->bitcount[0];
 
 
 
      // final transform
 
      sha512_private_transform (context, (uint64_t *) context->buffer);
 
 
 
      // save the hash data for output
 
#if __BYTE_ORDER__ ==  __ORDER_LITTLE_ENDIAN__
 
      for (int j = 0; j < 8; j++)
 
         context->state[j] = __builtin_bswap64 (context->state[j]); // convert to host byte order
 
#endif // __BYTE_ORDER__ ==  __ORDER_LITTLE_ENDIAN__
 
      memcpy (digest
, context
->state
, SHA512_DIGEST_LENGTH
);  
   }
 
 
 
   // zero out state data
 
   memset (context
, 0, sizeof (SHA512_CTX
));  
   #undef SHA512_SHORT_BLOCK_LENGTH
 
   return;
 
}
 
 
 
 
 
static uint8_t *SHA512 (void *data, size_t data_len, uint8_t *digest_or_NULL)
 
{
 
   // computes the SHA-512 hash of a block of data in one pass and write it to digest, or to a static buffer if NULL
 
   // returns the STRING REPRESENTATION of digest in a statically-allocated string
 
 
 
   static uint8_t static_digest[SHA512_DIGEST_LENGTH] = "";
 
   static char digest_as_string[2 * SHA512_DIGEST_LENGTH + 1] = "";
 
 
 
   SHA512_CTX ctx;
 
   size_t byte_index;
 
 
 
   SHA512_Init (&ctx);
 
   SHA512_Update (&ctx, data, data_len);
 
   if (digest_or_NULL == NULL)
 
      digest_or_NULL = static_digest;
 
   SHA512_Final (digest_or_NULL, &ctx);
 
 
 
   for (byte_index = 0; byte_index < SHA512_DIGEST_LENGTH; byte_index++)
 
      sprintf (&digest_as_string
[2 * byte_index
], "%02x", digest_or_NULL
[byte_index
]);  
   return (digest_as_string);
 
}
 
 
 
 
 
static int32_t update_checksum (const int32_t start_value, 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 };
 
   int32_t image_cksum = start_value;
 
   const char *current_char_ptr = data;
 
   size_t i;
 
 
 
   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 void hex_fprintf (FILE *fp, const uint8_t *data, size_t data_size, int howmany_columns, const char *fmt, ...)
 
{
 
   // this function logs hexadecimal data to an opened file pointer (or to stdout/stderr)
 
 
 
   va_list argptr;
 
   size_t index;
 
   int i;
 
 
 
   // concatenate all the arguments in one string and write it to the file
 
 
 
   // for each row of howmany_columns bytes of data...
 
   for (index = 0; index < data_size; index += howmany_columns)
 
   {
 
      fprintf (fp
, "    %05zu  ", index
); // print array address of row  
      for (i = 0; i < howmany_columns; i++)
 
         if (index + i < data_size)
 
            fprintf (fp
, " %02X", data
[index 
+ i
]); // if row contains data, print data as hex bytes  
         else
 
            fprintf (fp
, "   "); // else fill the space with blanks  
      for (i = 0; i < howmany_columns; i++)
 
         if (index + i < data_size)
 
            fputc ((data
[index 
+ i
] >= 32) && (data
[index 
+ i
] < 127) ? data
[index 
+ i
] : '.', fp
); // now if row contains data, print data as ASCII  
         else
 
            fputc (' ', fp
); // else fill the space with blanks  
   }
 
 
 
   return; // and return
 
}
 
 
 
 
 
static char *binary (const uint8_t x, char char_for_zero, char char_for_one)
 
{
 
   // returns the binary representation of x as a string
 
 
 
   static char outstr[9] = "00000000";
 
   for (int i = 0; i < 8; i++)
 
      outstr[i] = (x & (0x80 >> i) ? char_for_one : char_for_zero);
 
   return (outstr);
 
}
 
 
 
 
 
static char *describe_uint8 (const uint8_t x, const char *bitwise_stringdescs[8])
 
{
 
   // returns the ORed description of byte 'x' according to the description strings for each bit
 
 
 
   static char *default_bitstrings[8] = { "bit0", "bit1", "bit2", "bit3", "bit4", "bit5", "bit6", "bit7" };
 
   static char outstr[8 * 64] = "";
 
 
 
   outstr[0] = 0;
 
   for (int i = 0; i < 8; i++)
 
      if (x & (1 << i))
 
      {
 
         if (outstr[0] != 0)
 
         strcat (outstr
, ((bitwise_stringdescs 
!= NULL
) && (*bitwise_stringdescs
[i
] != 0) ? bitwise_stringdescs
[i
] : default_bitstrings
[i
]));  
      }
 
   return (outstr);
 
}
 
 
 
 
 
static int fwrite_filecontents (const char *pathname, FILE *fp)
 
{
 
   // dumps the binary contents of pathname to fp
 
 
 
   uint8_t *blob_buffer;
 
   size_t blob_size;
 
   FILE *blob_fp;
 
   int ret;
 
 
 
   blob_fp 
= fopen (pathname
, "rb"); 
   if (blob_fp == NULL)
 
      return (-1); // errno is set
 
 
 
   fseek (blob_fp
, 0, SEEK_END
);  
   blob_size 
= ftell (blob_fp
); 
   blob_buffer 
= malloc (blob_size
); 
   if (blob_buffer == NULL)
 
   {
 
      return (-1); // errno is set to ENOMEM
 
   }
 
   fseek (blob_fp
, 0, SEEK_SET
);  
   fread (blob_buffer
, 1, blob_size
, blob_fp
);  
 
 
   ret 
= (int) fwrite (blob_buffer
, 1, blob_size
, fp
); 
   return (ret);
 
}
 
 
 
 
 
static size_t fwrite_fsentry (const fsentry_t *fsentry, FILE *fp)
 
{
 
   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 (fp != NULL)
 
      fwrite (&fsentry
->header
, sizeof (fsentry
->header
), 1, fp
); // write the entry header (PACKED STRUCT)  
   count += sizeof (fsentry->header);
 
   if (S_ISREG (fsentry->header.mode))
 
   {
 
      if (fp != NULL)
 
      {
 
         fwrite (&fsentry
->u.
file.
offset, sizeof (uint32_t), 1, fp
); // write offset  
         fwrite (&fsentry
->u.
file.
size,   sizeof (uint32_t), 1, fp
); // write size  
      }
 
      count += 2 * sizeof (uint32_t);
 
      datalen 
= strlen (fsentry
->u.
file.
path) + 1; 
      if (fp != NULL)
 
         fwrite (fsentry
->u.
file.
path, (size_t) datalen
, 1, fp
); // write null-terminated path (no leading slash)  
      count += datalen;
 
   }
 
   else if (S_ISDIR (fsentry->header.mode))
 
   {
 
      datalen 
= strlen (fsentry
->u.
dir.
path) + 1; 
      if (fp != NULL)
 
         fwrite (fsentry
->u.
dir.
path, (size_t) datalen
, 1, fp
); // write null-terminated path (no leading slash)  
      count += datalen;
 
   }
 
   else if (S_ISLNK (fsentry->header.mode))
 
   {
 
      if (fp != NULL)
 
      {
 
         fwrite (&fsentry
->u.
symlink.
sym_offset, sizeof (uint16_t), 1, fp
); // write offset  
         fwrite (&fsentry
->u.
symlink.
sym_size,   sizeof (uint16_t), 1, fp
); // write size  
      }
 
      count += 2 * sizeof (uint16_t);
 
      datalen 
= strlen (fsentry
->u.
symlink.
path) + 1; 
      if (fp != NULL)
 
         fwrite (fsentry
->u.
symlink.
path, (size_t) datalen
, 1, fp
); // write null-terminated path (no leading slash)  
      count += datalen;
 
      datalen 
= strlen (fsentry
->u.
symlink.
contents) + 1; 
      if (fp != NULL)
 
         fwrite (fsentry
->u.
symlink.
contents, (size_t) datalen
, 1, fp
); // write null-terminated symlink contents  
      count += datalen;
 
   }
 
   else
 
   {
 
      if (fp != NULL)
 
      {
 
         fwrite (&fsentry
->u.
device.
dev,  sizeof (uint32_t), 1, fp
); // write dev number  
         fwrite (&fsentry
->u.
device.
rdev, sizeof (uint32_t), 1, fp
); // write rdev number  
      }
 
      count += 2 * sizeof (uint32_t);
 
      datalen 
= strlen (fsentry
->u.
device.
path) + 1; 
      if (fp != NULL)
 
         fwrite (fsentry
->u.
device.
path, (size_t) datalen
, 1, fp
); // write null-terminated path (no leading slash)  
      count += datalen;
 
   }
 
 
 
   if ((fp != NULL) && (count % image_align != 0))
 
      fwrite (zeropad_buffer
, count 
% image_align
, 1, fp
); // pad as necessary  
   count = ROUND_TO_UPPER_MULTIPLE (count, image_align);
 
 
 
   return (count);
 
}
 
 
 
 
 
static size_t add_fsentry (fsentry_t **fsentries, size_t *fsentry_count, const char *stored_pathname, parms_t *entry_parms, const uint8_t *entry_data, const size_t entry_datalen)
 
{
 
   static char candidate_pathname[1024];
 
   static char *MKIFS_PATH = NULL;
 
   static int inode_count = 0; // will be preincremented each time this function is called
 
 
 
   void *reallocated_ptr;
 
   char processor_base[16];
 
   struct stat stat_buf;
 
   uint8_t *data_buffer = NULL;
 
   size_t data_len = 0;
 
   uint32_t mtime 
= (uint32_t) time (NULL
);  
   uint32_t extra_ino_flags = 0;
 
   fsentry_t *fsentry;
 
   char *token;
 
   FILE *fp;
 
 
 
   if (S_ISDIR (entry_parms->st_mode)) // are we storing a directory ?
 
   {
 
      fprintf (stderr
, "directory: ino 0x%x uid %d gid %d mode 0%o path \"%s\"\n", 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 ((entry_data != NULL) && (entry_datalen > 0)) // was an explicit contents blob supplied ?
 
      {
 
         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
 
            data_len = entry_datalen;
 
            data_buffer 
= malloc (data_len
); 
            if (data_buffer == NULL)
 
            {
 
               fprintf (stderr
, "fatal error: out of memory\n");  
            }
 
            memcpy (data_buffer
, entry_data
, data_len
);  
 
 
            sprintf (candidate_pathname
, "%s/procnto-smp-instr", entry_parms
->prefix
); // fix the entry name  
            stored_pathname = candidate_pathname;
 
            extra_ino_flags = IFS_INO_PROCESSED_ELF | IFS_INO_BOOTSTRAP_EXE; // procnto needs to have these flags stamped on the inode
 
            image_kernel_ino = extra_ino_flags | (inode_count + 1);
 
         }
 
         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
 
 
 
         // should we substitute precompiled data to this data blob ?
 
         if ((entry_parms->precompiled_data != NULL) && (entry_parms->precompiled_datalen > 0))
 
         {
 
            data_len = entry_parms->precompiled_datalen;
 
            data_buffer 
= malloc (data_len
); 
            if (data_buffer == NULL)
 
            {
 
               fprintf (stderr
, "fatal error: out of memory\n");  
            }
 
            memcpy (data_buffer
, entry_parms
->precompiled_data
, data_len
);  
         }
 
         else // else use the supplied data blob
 
         {
 
            data_len = entry_datalen;
 
            data_buffer 
= malloc (data_len
); 
            if (data_buffer == NULL)
 
            {
 
               fprintf (stderr
, "fatal error: out of memory\n");  
            }
 
            memcpy (data_buffer
, entry_data
, data_len
);  
         }
 
/*
 
         else if (entry_parms->should_compile_contents_as_startup_script) // should we compile this contents as a startup script ?
 
         {
 
            // HACK: for now just use a precompiled script
 
            // FIXME: replace this with a true compilation with the rules defined above
 
            data_buffer = malloc (INITIAL_STARTUP_SCRIPT_LEN);
 
            if (data_buffer == NULL)
 
            {
 
               fprintf (stderr, "fatal error: out of memory\n");
 
               exit (1);
 
            }
 
            memcpy (data_buffer, INITIAL_STARTUP_SCRIPT, INITIAL_STARTUP_SCRIPT_LEN);
 
            data_len = INITIAL_STARTUP_SCRIPT_LEN;
 
            image_bootscript_ino = inode_count + 1; // save boot script inode number for image header
 
         }*/
 
 
 
         fprintf (stderr
, "file: ino 0x%x uid %d gid %d mode 0%o path \"%s\" blob (len %zd)\n", extra_ino_flags 
| (inode_count 
+ 1), entry_parms
->uid
, entry_parms
->gid
, entry_parms
->st_mode
, stored_pathname
, data_len
);  
      }
 
      else if ((entry_data != NULL) && (entry_data[0] != 0)) // else entry_datalen == 0, was some sort of pathname supplied ?
 
      {
 
         // is it an absolute pathname ?
 
         if (IS_DIRSEP (entry_data[0])
 
                 && (entry_data[1] == ':')
 
                 && IS_DIRSEP (entry_data[2])))
 
         {
 
            // in this case, it MUST exist at its designated location (either absolute or relative to the current working directory)
 
            strcpy (candidate_pathname
, entry_data
);  
            if (stat (candidate_pathname, &stat_buf) != 0)
 
            {
 
               fprintf (stderr
, "fatal error: filesystem entry \"%s\" specified in \"%s\" line %d not found on build host\n", entry_data
, buildfile_pathname
, lineno
);  
            }
 
         }
 
         else // the path is relative, search it among the search paths we have
 
         {
 
            // is the search path NOT defined ?
 
            if (entry_parms->search[0] == 0)
 
            {
 
               // initialize the default search path in MKIFS_PATH
 
               if (MKIFS_PATH == NULL)
 
               {
 
                  MKIFS_PATH 
= getenv ("MKIFS_PATH"); // look in the environment first, and construct a default one if not supplied 
                  if (MKIFS_PATH == NULL)
 
                  {
 
                     strcpy (processor_base
, image_processor
); // construct PROCESSOR_BASE  
                     token 
= strchr (processor_base
, '-'); 
                     if (token != NULL)
 
                        *token = 0; // split anything from the first dash onwards
 
                     data_len 
= strlen (processor_base
); 
                     if ((data_len > 2) && ((processor_base[data_len - 2] == 'b') || (processor_base[data_len - 2] == 'l')) && (processor_base[data_len - 1] == 'e'))
 
                        processor_base[data_len - 2] = 0; // if it ends with "le" or "be", strip that too
 
 
 
                     MKIFS_PATH 
= malloc (10 * MAXPATHLEN
); // construct a default MKIFS_PATH now 
                     if (MKIFS_PATH == NULL)
 
                     {
 
                        fprintf (stderr
, "fatal error: out of memory\n");  
                     }
 
                     sprintf (MKIFS_PATH
, ".|%s/%s/sbin|%s/%s/usr/sbin|%s/%s/boot/sys|%s/%s/boot/sys|%s/%s/bin|%s/%s/usr/bin|%s/%s/lib|%s/%s/lib/dll|%s/%s/usr/lib", // use a platform-agnostic character as path separator  
                                          QNX_TARGET, image_processor,
 
                                          QNX_TARGET, image_processor,
 
                                          QNX_TARGET, image_processor,
 
                                          QNX_TARGET, processor_base,
 
                                          QNX_TARGET, image_processor,
 
                                          QNX_TARGET, image_processor,
 
                                          QNX_TARGET, image_processor,
 
                                          QNX_TARGET, image_processor,
 
                                          QNX_TARGET, image_processor);
 
                  }
 
               } // at this point MKIFS_PATH is defined
 
 
 
               strcpy (entry_parms
->search
, MKIFS_PATH
); // if entry search path is not defined, use the contents of MKIFS_PATH  
            }
 
 
 
            // convert path separators in the MKIFS_PATH environment variable into something platform-agnostic
 
            for (token = entry_parms->search; *token != 0; token++)
 
               if (*token == PATH_SEP)
 
                  *token = '|';
 
 
 
            token 
= strtok (entry_parms
->search
, "|"); 
            while (token != NULL)
 
            {
 
               sprintf (candidate_pathname
, "%s/%s", token
, entry_data
);  
               if (stat (candidate_pathname, &stat_buf) == 0)
 
                  break;
 
            }
 
            if (token == NULL)
 
            {
 
               fprintf (stderr
, "fatal error: filesystem entry \"%s\" specified in \"%s\" line %d not found on build host\n", entry_data
, buildfile_pathname
, lineno
);  
            }
 
         }
 
 
 
         data_len = stat_buf.st_size;
 
         mtime = (uint32_t) stat_buf.st_mtime;
 
 
 
         data_buffer 
= malloc (data_len
); 
         if (data_buffer == NULL)
 
         {
 
            fprintf (stderr
, "fatal error: out of memory\n");  
         }
 
         fp 
= fopen (candidate_pathname
, "rb"); 
         fread (data_buffer
, 1, data_len
, fp
);  
 
 
         fprintf (stderr
, "file: ino 0x%x uid %d gid %d mode 0%o path \"%s\" buildhost_file \"%s\" (len %zd)\n", inode_count 
+ 1, entry_parms
->uid
, entry_parms
->gid
, entry_parms
->st_mode
, stored_pathname
, entry_data
, data_len
);  
      }
 
   }
 
   else if (S_ISLNK (entry_parms->st_mode)) // else are we storing a symbolic link ?
 
   {
 
      data_len 
= strlen (entry_data
); 
 
 
      data_buffer 
= malloc (data_len 
+ 1); 
      if (data_buffer == NULL)
 
      {
 
         fprintf (stderr
, "fatal error: out of memory\n");  
      }
 
      memcpy (data_buffer
, entry_data
, data_len 
+ 1); // copy including null terminator  
 
 
      fprintf (stderr
, "symlink: ino 0x%x uid %d gid %d mode 0%o path \"%s\" -> \"%s\"\n", inode_count 
+ 1, entry_parms
->uid
, entry_parms
->gid
, entry_parms
->st_mode
, stored_pathname
, data_buffer
);  
   }
 
   else // we must be storing a FIFO
 
   {
 
      if (strchr (entry_data
, ':') == NULL
)  
      {
 
         fprintf (stderr
, "fatal error: device entry \"%s\" malformed (no 'dev:rdev' pair)\n", stored_pathname
);  
      }
 
 
 
      fprintf (stderr
, "fifo: ino 0x%x uid %d gid %d mode 0%o path \"%s\" dev rdev %s)\n", inode_count 
+ 1, entry_parms
->uid
, entry_parms
->gid
, entry_parms
->st_mode
, stored_pathname
, entry_data
);  
   }
 
 
 
   // reallocate filesystem entries array to hold one more slot
 
   reallocated_ptr 
= realloc (*fsentries
, (*fsentry_count 
+ 1) * sizeof (fsentry_t
)); // attempt to reallocate 
   if (reallocated_ptr == NULL)
 
   {
 
      fprintf (stderr
, "fatal error: out of memory\n");  
   }
 
   *fsentries = reallocated_ptr; // save reallocated pointer
 
   fsentry = &(*fsentries)[*fsentry_count]; // quick access to fs entry slot
 
   //fsentry->header.size = 0; // will be filled once we know it
 
   fsentry->header.extattr_offset = 0;
 
   fsentry->header.ino = 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 = mtime;
 
   if (S_ISDIR (entry_parms->st_mode))
 
   {
 
      fsentry->u.dir.path = strdup (stored_pathname);
 
      fsentry
->header.
size = (uint16_t) (sizeof (fsentry
->header
) + ROUND_TO_UPPER_MULTIPLE 
(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) data_len;
 
      fsentry->u.file.path = strdup (stored_pathname);
 
      fsentry->u.file.UNSAVED_databuf = data_buffer;
 
      fsentry
->header.
size = (uint16_t) (sizeof (fsentry
->header
) + ROUND_TO_UPPER_MULTIPLE 
(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
) + 1); 
      fsentry->u.symlink.sym_size = (uint16_t) data_len;
 
      fsentry->u.symlink.path = strdup (stored_pathname);
 
      fsentry->u.symlink.contents = data_buffer;
 
      fsentry->header.size = (uint16_t) (sizeof (fsentry->header) + ROUND_TO_UPPER_MULTIPLE ((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
 
   {
 
      fsentry
->u.
device.
dev  = strtol (entry_data
, NULL
, 0); // use strtol() to parse decimal (...), hexadecimal (0x...) and octal (0...) numbers 
      fsentry
->u.
device.
rdev = strtol (strchr (entry_data
, ':') + 1, NULL
, 0); // use strtol() to parse decimal (...), hexadecimal (0x...) and octal (0...) numbers 
      fsentry->u.device.path = strdup (stored_pathname);
 
      fsentry
->header.
size = (uint16_t) (sizeof (fsentry
->header
) + ROUND_TO_UPPER_MULTIPLE 
(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)++;
 
   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 int fsentry_compare_sizes_cb (const void *a, const void *b)
 
{
 
   // qsort() callback that compares two imagefs filesystem entries and sort them by increasing data size
 
 
 
   const fsentry_t *entry_a = (const fsentry_t *) a;
 
   const fsentry_t *entry_b = (const fsentry_t *) b;
 
   const int32_t size_a = (S_ISREG (entry_a->header.mode) ? entry_a->u.file.size : 0); // only files (i.e. entries wearing the S_IFREG flag) have a separate data block
 
   const int32_t size_b = (S_ISREG (entry_b->header.mode) ? entry_b->u.file.size : 0); // only files (i.e. entries wearing the S_IFREG flag) have a separate data block
 
   return ((int) size_b - (int) size_a);
 
}
 
 
 
 
 
int main (int argc, char **argv)
 
{
 
   // program entrypoint
 
 
 
   #define PAD_OUTFILE_TO(val) do { curr_offset = ftell (fp); while (curr_offset < (val)) { putc (0, fp); curr_offset++; } } while (0)
 
 
 
   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 = { 0755, 0644, 0, 0, S_IFREG, "/proc/boot", false, "", NULL, 0 }; // default parameters for a filesystem entry
 
   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)
 
 
 
   // bootable IFS support
 
   char *bootfile_pathname = NULL;           // HACK: pathname to bootcode binary blob file to put at the start of a bootable IFS
 
   size_t bootfile_size = 0;                 // HACK: size of the bootcode binary blob file to put at the start of a bootable IFS
 
   char *startupfile_pathname = NULL;        // HACK: pathname to precompiled startup file blob to put in the startup header of a bootable IFS
 
   size_t startupfile_ep_from_imagebase = 0; // HACK: startup code entrypoint offset from image base for a bootable IFS 
 
   char *kernelfile_pathname = NULL;         // HACK: pathname to precompiled kernel file blob to put in a bootable IFS
 
   size_t kernelfile_offset = 0;             // HACK: kernel file offset in bootable IFS
 
 
 
   char path_in_ifs[MAXPATHLEN];
 
   char *ifs_pathname = NULL;
 
   void *reallocated_ptr;
 
   struct stat stat_buf;
 
   size_t startuptrailer_offset;
 
   size_t startupheader_offset;
 
   size_t imgtrailer_offset;
 
   size_t imgheader_offset;
 
   size_t imgdir_offset;
 
   size_t imgdir_size;
 
   size_t final_size;
 
   size_t fsentry_index;
 
   size_t curr_offset;
 
   size_t blob_datasize; // mallocated size
 
   size_t blob_datalen; // used size
 
   uint8_t *blob_data = NULL; // mallocated
 
   char *line_ptr;
 
   char *directiveblock_start;
 
   char *write_ptr;
 
   char *token;
 
   char *value;
 
   char *sep;
 
   //char *ctx;
 
   int arg_index;
 
   bool is_quoted_context = false;
 
   bool is_escaped_char = false;
 
   bool want_info = false;
 
   bool want_help = false;
 
   bool is_foreign_endianness;
 
   int string_len;
 
   int read_char;
 
   FILE *buildfile_fp;
 
   FILE *fp;
 
 
 
   // 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))
 
         {
 
            fprintf (stderr
, "error: the --startupfile arguments expects <pathname>@<entrypoint_from_image_base>\n");  
         }
 
         *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))
 
         {
 
            fprintf (stderr
, "error: the --kernelfile arguments expects <pathname>@<fileoffset>\n");  
         }
 
         *sep = 0;
 
         kernelfile_pathname = argv[arg_index];
 
         kernelfile_offset = (size_t) read_integer (sep + 1);
 
      }
 
      else if (strcmp (argv
[arg_index
], "--info") == 0)  
         want_info = true;
 
      else if ((strcmp (argv
[arg_index
], "-?") == 0) || (strcmp (argv
[arg_index
], "--help") == 0))  
         want_help = true;
 
      else if (buildfile_pathname == NULL)
 
         buildfile_pathname = argv[arg_index];
 
      else if (ifs_pathname == NULL)
 
         ifs_pathname = argv[arg_index];
 
   }
 
 
 
   // do we not have enough information to run ?
 
   if (want_help || (buildfile_pathname == NULL) || (!want_info && (ifs_pathname == NULL)))
 
   {
 
      fprintf ((want_help 
? stdout 
: stderr
), "ifstool - QNX in-kernel filesystem creation utility by Pierre-Marie Baty <pm@pmbaty.com>\n");  
      fprintf ((want_help 
? stdout 
: stderr
), "          version " VERSION_FMT_YYYYMMDD 
"\n", VERSION_ARG_YYYYMMDD
);  
      if (!want_help)
 
         fprintf (stderr
, "error: missing parameters\n");  
      fprintf ((want_help 
? stdout 
: stderr
), "usage:\n");  
      fprintf ((want_help 
? stdout 
: stderr
), "    ifstool [--bootfile <pathname>] [--startupfile <pathname>@<EP_from_imgbase>] [--kernelfile <pathname>@<fileoffs>] <buildfile> <outfile>\n");  
      fprintf ((want_help 
? stdout 
: stderr
), "    ifstool --info <ifs file>\n");  
      fprintf ((want_help 
? stdout 
: stderr
), "    ifstool --help\n");  
      exit (want_help 
? 0 : 1);  
   }
 
 
 
   // do we want info about a particular IFS ? if so, dump it
 
   if (want_info)
 
      exit (dump_ifs_info 
(buildfile_pathname
)); // NOTE: the first argument after --info is actually the IFS file, not a build file, but the arguments are collected in this order  
 
 
   // make sure we have ${QNX_TARGET} pointing somewhere
 
   QNX_TARGET 
= getenv ("QNX_TARGET"); 
   if (QNX_TARGET == NULL)
 
   {
 
      fprintf (stderr
, "error: the QNX_TARGET environment variable is not set\n");  
   }
 
   else if (access (QNX_TARGET, 0) != 0)
 
   {
 
      fprintf (stderr
, "error: the QNX_TARGET environment variable doesn't point to an existing directory\n");  
   }
 
 
 
   // open build file
 
   buildfile_fp 
= fopen (buildfile_pathname
, "rb"); 
   if (buildfile_fp == NULL)
 
   {
 
      fprintf (stderr
, "error: unable to open build file \"%s\" for reading (%s)\n", 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, 0); // add the root dir first
 
 
 
   while (fgets (line_buffer
, sizeof (line_buffer
), buildfile_fp
) != NULL
)  
   {
 
      lineno++; // keep track of current line number
 
      //fprintf (stderr, "read buildfile line %d: {%s}\n", lineno, line_buffer);
 
 
 
      line_ptr = line_buffer;
 
      while ((*line_ptr 
!= 0) && isspace (*line_ptr
))  
         line_ptr++; // skip leading spaces
 
 
 
      if ((*line_ptr == 0) || (*line_ptr == '#'))
 
         continue; // 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 (default_parms
));  
 
 
      //fprintf (stderr, "parsing buildfile line %d: [%s]\n", lineno, line_ptr);
 
 
 
      // does this line start with an attribute block ?
 
      if (*line_ptr == '[')
 
      {
 
         line_ptr++; // skip the leading square bracket
 
         directiveblock_start = line_ptr; // remember where it starts
 
         is_quoted_context = false;
 
         while ((*line_ptr != 0) && !((*line_ptr == ']') && (line_ptr[-1] != '\\')))
 
         {
 
            if (*line_ptr == '"')
 
               is_quoted_context ^= true; // remember when we're between quotes
 
            else if (!is_quoted_context && (*line_ptr == ' '))
 
               *line_ptr = RECORD_SEP; // 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 != ']')
 
         {
 
            fprintf (stderr
, "warning: syntax error in \"%s\" line %d: unterminated attributes block (skipping)\n", buildfile_pathname
, lineno
);  
            continue; // 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 (directiveblock_start
, RECORD_SEP_STR
); 
         while (token != NULL)
 
         {
 
            // evaluate attribute token
 
            #define REACH_TOKEN_VALUE() do { value = strchr (token, '=') + 1; if (*value == '"') value++; } while (0)
 
            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 
(); entry_parms.
st_mode = (strcmp (value
, "dir") == 0 ? S_IFDIR 
: (strcmp (value
, "file") == 0 ? S_IFREG 
: (strcmp (value
, "link") == 0 ? S_IFLNK 
: (strcmp (value
, "fifo") == 0 ? S_IFIFO 
: (fprintf (stderr
, "warning: invalid 'type' attribute in \"%s\" line %d: '%s', defaulting to 'file'\n", buildfile_pathname
, lineno
, value
), S_IFREG
))))); }  
            else if (strncmp (token
, "prefix=",  7) == 0) { REACH_TOKEN_VALUE 
(); strcpy (entry_parms.
prefix, (*value 
== '/' ? value 
+ 1 : value
)); } // skip possible leading slash in prefix  
            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 (FIXME: 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  
               fprintf (stderr
, "info: image 0x%x-0x%x maxsize %d totalsize %d align %d\n", 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
 
               {
 
                  fprintf (stderr
, "error: creating bootable images require the --bootfile, --startupfile and --kernelfile command-line options in \"%s\" line %d\n", buildfile_pathname
, lineno
);  
               }
 
               if ((sep 
= strchr (value
, ',')) != NULL
) // do we have a comma separating (optional) processor and boot file name ?  
               {
 
                  *sep = 0;
 
                  strcpy (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 (FIXME: 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)
 
               {
 
                  fprintf (stderr
, "error: unable to stat the boot file \"%s\" specified in \"%s\" line %d: %s\n", bootfile_pathname
, buildfile_pathname
, lineno
, strerror (errno
));  
               }
 
               bootfile_size = stat_buf.st_size; // save preboot file size
 
               fprintf (stderr
, "info: processor \"%s\" bootfile \"%s\"\n", image_processor
, bootfile_pathname
);  
               if (stat (kernelfile_pathname, &stat_buf) != 0)
 
               {
 
                  fprintf (stderr
, "fatal error: unable to read precompiled kernel file \"%s\" specified in --kernelfile argument\n", kernelfile_pathname
);  
               }
 
               entry_parms.
precompiled_data = malloc (stat_buf.
st_size); 
               if (entry_parms.precompiled_data == NULL)
 
               {
 
                  fprintf (stderr
, "fatal error: out of memory\n");  
               }
 
               fp 
= fopen (kernelfile_pathname
, "rb"); 
               fread (entry_parms.
precompiled_data, 1, stat_buf.
st_size, fp
);  
               entry_parms.precompiled_datalen = stat_buf.st_size;
 
            }
 
            else if (strcmp (token
, "+script") == 0) {  
               entry_parms.is_compiled_bootscript = true;
 
               entry_parms.precompiled_data = INITIAL_STARTUP_SCRIPT; // HACK until the script compiler is implemented
 
               entry_parms.precompiled_datalen = sizeof (INITIAL_STARTUP_SCRIPT) - 1;
 
            }
 
            else fprintf (stderr
, "warning: unimplemented attribute in \"%s\" line %d: '%s'\n", buildfile_pathname
, lineno
, token
);  
            #undef REACH_TOKEN_VALUE
 
 
 
            token 
= strtok (NULL
, RECORD_SEP_STR
); // 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) { \
 
                  fprintf (stderr, "info: changing default " descr " from " fmt " to " fmt " by attribute at \"%s\" line %d\n", 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 (strcmp (entry_parms.attr, default_parms.attr) != 0) { \
 
                  fprintf (stderr, "info: changing default " descr " from " fmt " to " fmt " by attribute at \"%s\" line %d\n", default_parms.attr, entry_parms.attr, buildfile_pathname, lineno); \
 
                  strcpy (default_parms.attr, entry_parms.attr); \
 
               } } while (0)
 
            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_STR (prefix,  "prefix",                "\"%s\"");
 
            APPLY_DEFAULT_ATTR_NUM (is_compiled_bootscript, "compiled script state", "%d");
 
            #undef APPLY_DEFAULT_ATTR_STR
 
            #undef APPLY_DEFAULT_ATTR_NUM
 
            continue; // 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 (path_in_ifs
, "%s", 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
 
      if (*line_ptr == '/')
 
      {
 
         fprintf (stderr
, "warning: paths in the IFS file should not begin with a leading '/' in \"%s\" line %d\n", buildfile_pathname
, lineno
);  
         line_ptr++; // consistency check: paths in the IFS should not begin with a '/'
 
      }
 
      while ((*line_ptr 
!= 0) && (*line_ptr 
!= '=') && !isspace (*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
 
 
 
      // 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
 
 
 
      blob_data = NULL;
 
      blob_datasize = 0;
 
      blob_datalen = 0;
 
 
 
      // 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)
 
         {
 
            fprintf (stderr
, "warning: syntax error in \"%s\" line %d: missing data specification after equal sign (skipping)\n", buildfile_pathname
, lineno
);  
            continue; // 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 == '{')
 
         {
 
            line_ptr++; // skip the leading content definition
 
            is_escaped_char = false;
 
            for (;;)
 
            {
 
               read_char 
= fgetc (buildfile_fp
); 
               if (read_char == EOF)
 
               {
 
                  fprintf (stderr
, "fatal error: syntax error in \"%s\" line %d: unterminated contents block (end of file reached)\n", buildfile_pathname
, lineno
);  
                  exit (1); // invalid contents block  
               }
 
               else if (read_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 (blob_datalen == blob_datasize) // reallocate in 4 kb blocks
 
                  {
 
                     reallocated_ptr 
= realloc (blob_data
, blob_datasize 
+ 4096); 
                     if (reallocated_ptr == NULL)
 
                     {
 
                        fprintf (stderr
, "fatal error: out of memory\n");  
                     }
 
                     blob_data = reallocated_ptr;
 
                     blob_datasize += 4096;
 
                  }
 
                  blob_data[blob_datalen++] = read_char;
 
                  if (read_char == '\n')
 
                     lineno++;
 
               }
 
            }
 
         }
 
         else // not a content definition between { brackets }, meaning it's a build host filesystem path
 
         {
 
            blob_data = line_ptr; // explicit pathname on build host
 
            blob_datalen = 0;
 
         }
 
      }
 
      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)
 
         {
 
            fprintf (stderr
, "warning: syntax error in \"%s\" line %d: missing equal sign and symlink target (skipping)\n", buildfile_pathname
, lineno
);  
            continue; // invalid symlink specification, skip line
 
         }
 
 
 
         blob_data 
= &path_in_ifs
[strlen (entry_parms.
prefix) + 1]; // same pathname 
         blob_datalen = 0;
 
      }
 
 
 
      // now add this entry to the image filesystem
 
      entry_parms.st_mode |= (S_ISDIR (entry_parms.st_mode) ? entry_parms.dperms : entry_parms.perms); // complete entry permissions
 
      add_fsentry (&fsentries, &fsentry_count, path_in_ifs, &entry_parms, blob_data, blob_datalen); // and add filesystem entry
 
      if (blob_datasize > 0)
 
         free (blob_data
); // if blob data was allocated, free it  
   }
 
 
 
   // sort the filesystem entries by pathname
 
   qsort (&fsentries
[1], fsentry_count 
- 1, sizeof (fsentry_t
), fsentry_compare_pathnames_cb
);  
 
 
   // calculate filesystem entries size
 
   imgdir_size = sizeof (image_header);
 
   for (fsentry_index = 0; fsentry_index < fsentry_count; fsentry_index++)
 
   {
 
      fprintf (stderr
, "info: sorted entry: %s\n", (S_ISDIR 
(fsentries
[fsentry_index
].
header.
mode) ? fsentries
[fsentry_index
].
u.
dir.
path : (S_ISREG 
(fsentries
[fsentry_index
].
header.
mode) ? fsentries
[fsentry_index
].
u.
file.
path : (S_ISLNK 
(fsentries
[fsentry_index
].
header.
mode) ? fsentries
[fsentry_index
].
u.
symlink.
path : fsentries
[fsentry_index
].
u.
device.
path))));  
      imgdir_size += fwrite_fsentry (&fsentries[fsentry_index], NULL);
 
   }
 
   fprintf (stderr
, "info: image directory size: %zd (0x%zx)\n", imgdir_size
, imgdir_size
);  
 
 
   // write IFS file
 
   fp 
= fopen (ifs_pathname
, "wb"); 
   if (fp == NULL)
 
   {
 
      fprintf (stderr
, "error: failed to open \"%s\" for writing (%s)\n", ifs_pathname
, strerror (errno
));  
   }
 
 
 
   // do we have a startup file ? if so, this is a bootable image
 
   if (startupfile_pathname != NULL)
 
   {
 
      // write boot prefix
 
      fwrite_filecontents (bootfile_pathname, fp);
 
      PAD_OUTFILE_TO 
(ROUND_TO_UPPER_MULTIPLE 
(ftell (fp
), image_align
)); // pad as necessary 
 
 
      startupheader_offset 
= ftell (fp
); // save startup header offset 
      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 = STARTUP_HDR_MACHINE_X86_64; // EM_X86_64
 
      else if (strcmp (image_processor
, "aarch64le") == 0)  
         startup_header.machine = STARTUP_HDR_MACHINE_AARCH64; // EM_AARCH64
 
      else
 
      {
 
         fprintf (stderr
, "fatal error: unsupported processor type '%s' found in build file \"%s\"\n", image_processor
, buildfile_pathname
);  
      }
 
      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))
 
      fwrite (&startup_header
, sizeof (startup_header
), 1, fp
); // write startup header  
      PAD_OUTFILE_TO 
(ROUND_TO_UPPER_MULTIPLE 
(ftell (fp
), image_align
)); // pad as necessary 
 
 
      // ######################################################################################################################################################################################################################################
 
      // # 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=0x1401030 --no-relax ${QNX_TARGET}/x86_64/boot/sys/startup-x86 -o startup.bin.UNSTRIPPED
 
      // ######################################################################################################################################################################################################################################
 
      fwrite_filecontents (startupfile_pathname, fp); // write startup code from blob file
 
      PAD_OUTFILE_TO 
(ROUND_TO_UPPER_MULTIPLE 
(ftell (fp
), image_align
)); // pad as necessary 
 
 
      startuptrailer_offset 
= ftell (fp
); // save startup trailer offset 
      fwrite (&startup_trailer
, sizeof (startup_trailer
), 1, fp
); // write startup trailer  
      PAD_OUTFILE_TO 
(ROUND_TO_UPPER_MULTIPLE 
(ftell (fp
), image_align
)); // pad as necessary 
   }
 
 
 
   imgheader_offset 
= ftell (fp
); // save image header offset 
   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  = (uint32_t) imgdir_size; // 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")
 
   fwrite (&image_header
, sizeof (image_header
), 1, fp
); // write image header  
   PAD_OUTFILE_TO 
(ROUND_TO_UPPER_MULTIPLE 
(ftell (fp
), image_align
)); // pad as necessary 
 
 
   // write image directory (with the wrong file offsets)
 
   imgdir_offset 
= ftell (fp
); 
   for (fsentry_index = 0; fsentry_index < fsentry_count; fsentry_index++)
 
      fwrite_fsentry (&fsentries[fsentry_index], fp); // NOTE: padding is handled in this function
 
   PAD_OUTFILE_TO 
(ROUND_TO_UPPER_MULTIPLE 
(ftell (fp
), image_align
)); // pad as necessary 
 
 
   // is it a bootable image with a kernel file ?
 
   if ((startupfile_pathname != NULL) && (kernelfile_pathname != NULL))
 
   {
 
      // sort the filesystem entries by sizes
 
      qsort (fsentries
, fsentry_count
, sizeof (fsentry_t
), fsentry_compare_sizes_cb
);  
 
 
      // write as many small files as we can before reaching the kernel offset 
 
      for (fsentry_index = 0; fsentry_index < fsentry_count; fsentry_index++)
 
      {
 
         if (!S_ISREG (fsentries[fsentry_index].header.mode) || fsentries[fsentry_index].UNSAVED_was_data_written)
 
            continue; // skip all entries that don't have a separate data block and those who were written already
 
         curr_offset 
= ftell (fp
); 
         if (curr_offset + fsentries[fsentry_index].u.file.size >= kernelfile_offset)
 
            break; // stop writing entries as soon as we reach the kernel file offset
 
         fsentries[fsentry_index].u.file.offset = (uint32_t) (curr_offset - imgheader_offset); // save file data blob offset in file structure
 
         fwrite (fsentries
[fsentry_index
].
u.
file.
UNSAVED_databuf, 1, fsentries
[fsentry_index
].
u.
file.
size, fp
); // write file data blob  
         PAD_OUTFILE_TO 
(ROUND_TO_UPPER_MULTIPLE 
(ftell (fp
), image_align
)); // pad as necessary 
         fsentries[fsentry_index].UNSAVED_was_data_written = true; // and remember this file's data was written
 
      }
 
      PAD_OUTFILE_TO (kernelfile_offset); // reach the kernel offset
 
 
 
      // ######################################################################################################################################################################################################################################
 
      // # 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
 
      // ######################################################################################################################################################################################################################################
 
      fwrite_filecontents (kernelfile_pathname, fp); // write kernel from blob file
 
      PAD_OUTFILE_TO 
(ROUND_TO_UPPER_MULTIPLE 
(ftell (fp
), image_align
)); // pad as necessary 
   }
 
 
 
   // then write all the other files
 
   for (fsentry_index = 0; fsentry_index < fsentry_count; fsentry_index++)
 
   {
 
      if (!S_ISREG (fsentries[fsentry_index].header.mode) || fsentries[fsentry_index].UNSAVED_was_data_written)
 
         continue; // skip all entries that don't have a separate data block and those who were written already
 
      curr_offset 
= ftell (fp
); 
      fsentries[fsentry_index].u.file.offset = (uint32_t) (curr_offset - imgheader_offset); // save file data blob offset in file structure
 
      fwrite (fsentries
[fsentry_index
].
u.
file.
UNSAVED_databuf, 1, fsentries
[fsentry_index
].
u.
file.
size, fp
); // write file data blob  
      PAD_OUTFILE_TO 
(ROUND_TO_UPPER_MULTIPLE 
(ftell (fp
), image_align
)); // pad as necessary 
      fsentries[fsentry_index].UNSAVED_was_data_written = true; // and remember this file's data was written
 
   }
 
 
 
   // finally, write trailer (including empty checksum)
 
   imgtrailer_offset 
= ftell (fp
); // save image trailer offset 
   fwrite (&image_trailer
, sizeof (image_trailer
), 1, fp
); // write image trailer  
   PAD_OUTFILE_TO 
(ROUND_TO_UPPER_MULTIPLE 
(ftell (fp
), image_align
)); // pad as necessary 
 
 
   // if we need to pad it to a specific length, do so
 
   PAD_OUTFILE_TO (image_totalsize);
 
 
 
   // see if we are past the image max size, in which case it's an error
 
   if (final_size > image_maxsize)
 
   {
 
      fprintf (stderr
, "error: image file \"%s\" size %zd exceeds max size (%zd)\n", ifs_pathname
, final_size
, (size_t) image_maxsize
);  
   }
 
 
 
   // do we have a startup file ? if so, this is a bootable image
 
   if (startupfile_pathname != NULL)
 
   {
 
      // rewrite startup header with final values
 
      fseek (fp
, startupheader_offset
, SEEK_SET
);  
      startup_header.startup_size = (uint32_t) (imgheader_offset - startupheader_offset); // size of startup header up to image header
 
      startup_header.imagefs_size = (uint32_t) (final_size - imgheader_offset); // size of uncompressed imagefs
 
      startup_header.ram_size = (uint32_t) final_size; // FIXME: this is necessarily a bit less, but should we really bother calculating the right size ?
 
      startup_header.stored_size = startup_header.ram_size;
 
      fwrite (&startup_header
, sizeof (startup_header
), 1, fp
); // write startup header  
 
 
      // compute SHA-512 checksum and V1 checksum of startup block
 
      blob_datasize = startuptrailer_offset - startupheader_offset;
 
      blob_data 
= malloc (blob_datasize
); 
      if (blob_data == NULL)
 
      {
 
         fprintf (stderr
, "fatal error: out of memory\n");  
      }
 
      fseek (fp
, startupheader_offset
, SEEK_SET
);  
      fread (blob_data
, 1, blob_datasize
, fp
);  
      SHA512 (blob_data, blob_datasize, startup_trailer.sha512); // compute SHA512 checksum
 
      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
 
      startup_trailer.cksum = 0; // compute old checksum
 
      startup_trailer.cksum = update_checksum (startup_trailer.cksum, (const uint32_t *) blob_data, blob_datasize, is_foreign_endianness);
 
      startup_trailer.cksum = update_checksum (startup_trailer.cksum, (const uint32_t *) startup_trailer.sha512, sizeof (startup_trailer.sha512), is_foreign_endianness);
 
 
 
      // rewrite startup trailer with final values
 
      fseek (fp
, startuptrailer_offset
, SEEK_SET
);  
      fwrite (&startup_trailer
, sizeof (startup_trailer
), 1, fp
); // write startup trailer  
   }
 
 
 
   // rewrite image header with final values
 
   fseek (fp
, imgheader_offset
, SEEK_SET
);  
   image_header.image_size = (uint32_t) (final_size - imgheader_offset); // size of uncompressed imagefs
 
   fwrite (&image_header
, sizeof (image_header
), 1, fp
); // write image header  
 
 
   // rewrite image directory with final checksum values
 
   fseek (fp
, imgdir_offset
, SEEK_SET
);  
   for (fsentry_index = 0; fsentry_index < fsentry_count; fsentry_index++)
 
      fwrite_fsentry (&fsentries[fsentry_index], fp);
 
 
 
   // compute SHA-512 checksum and V1 checksum of image block
 
   blob_datasize = imgtrailer_offset - imgheader_offset;
 
   blob_data 
= malloc (blob_datasize
); 
   if (blob_data == NULL)
 
   {
 
      fprintf (stderr
, "fatal error: out of memory\n");  
   }
 
   fseek (fp
, imgheader_offset
, SEEK_SET
);  
   fread (blob_data
, 1, blob_datasize
, fp
);  
   SHA512 (blob_data, blob_datasize, image_trailer.sha512); // compute SHA512 checksum
 
   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
 
   image_trailer.cksum = 0; // compute old checksum
 
   image_trailer.cksum = update_checksum (image_trailer.cksum, (const uint32_t *) blob_data, blob_datasize, is_foreign_endianness);
 
   image_trailer.cksum = update_checksum (image_trailer.cksum, (const uint32_t *) image_trailer.sha512, sizeof (image_trailer.sha512), is_foreign_endianness);
 
 
 
   // rewrite image trailer with final checksum values
 
   fseek (fp
, imgtrailer_offset
, SEEK_SET
);  
   fwrite (&image_trailer
, sizeof (image_trailer
), 1, fp
); // write image trailer  
 
 
   // finished, close IFS file and exit with a success code
 
}
 
 
 
 
 
static int dump_ifs_info (const char *ifs_pathname)
 
{
 
   #define hex_printf(buf,size,...) hex_fprintf (stdout, (buf), (size), 16, __VA_ARGS__) // use 16 columns in hex output to stdout
 
   #define BINARY(x) binary ((x), '-', 'x')
 
 
 
   static const char *startupheader_flags1_strings[8] = {
 
      "VIRTUAL", // bit 0
 
      "BIGENDIAN", // bit 1
 
      "COMPRESS_BIT1", // bit 2
 
      "COMPRESS_BIT2", // bit 3
 
      "COMPRESS_BIT3", // bit 4
 
      "TRAILER_V2", // bit 5
 
      "", // bit 6
 
      "", // bit 7
 
   };
 
   static const char *imageheader_flags_strings[8] = {
 
      "BIGENDIAN", // bit 0
 
      "READONLY", // bit 1
 
      "INO_BITS", // bit 2
 
      "SORTED", // bit 3
 
      "TRAILER_V2", // bit 4
 
      "", // bit 5
 
      "", // bit 6
 
      "", // bit 7
 
   };
 
 
 
   startup_header_t *startup_header = NULL;
 
   startup_trailer_v1_t *startup_trailer_v1 = NULL;
 
   startup_trailer_v2_t *startup_trailer_v2 = NULL;
 
   image_header_t *image_header = NULL;
 
   size_t imageheader_offset = 0;
 
   image_trailer_v1_t *image_trailer_v1 = NULL;
 
   image_trailer_v2_t *image_trailer_v2 = NULL;
 
   size_t imagetrailer_offset = 0;
 
   fsentry_t **fsentries = NULL; // mallocated
 
   size_t fsentry_count = 0;
 
   fsentry_t *current_fsentry = NULL;
 
   char recorded_sha512[2 * SHA512_DIGEST_LENGTH + 1] = "";
 
   char computed_sha512[2 * SHA512_DIGEST_LENGTH + 1] = "";
 
   size_t startupfile_blobsize = 0;
 
   void *reallocated_ptr;
 
   bool is_foreign_endianness;
 
   size_t bootfile_blobsize = 0;
 
   size_t current_offset;
 
   size_t fsentry_index;
 
   size_t nearest_distance;
 
   size_t nearest_index;
 
   size_t byte_index;
 
   uint32_t recorded_checksum;
 
   uint32_t computed_checksum;
 
   uint8_t *filedata;
 
   size_t filesize;
 
   time_t mtime;
 
   FILE *fp;
 
 
 
   // open and read IFS file
 
   fp 
= fopen (ifs_pathname
, "rb"); 
   if (fp == NULL)
 
   {
 
      fprintf (stderr
, "error: can't open \"%s\" for reading: %s\n", ifs_pathname
, strerror (errno
));  
      return (1);
 
   }
 
   if (filedata == NULL)
 
   {
 
      fprintf (stderr
, "fatal error: out of memory\n");  
   }
 
   fread (filedata
, 1, filesize
, fp
);  
 
 
   printf ("QNX In-kernel Filesystem analysis produced by ifstool version " VERSION_FMT_YYYYMMDD 
"\n", VERSION_ARG_YYYYMMDD
);  
   printf ("IFS file \"%s\" - size 0x%zx (%zd) bytes\n", ifs_pathname
, filesize
, filesize
);  
 
 
   // parse file from start to end
 
   current_offset = 0;
 
   for (;;)
 
   {
 
      // does a startup header start here ?
 
      if ((current_offset 
+ sizeof (startup_header_t
) < filesize
) && (memcmp (&filedata
[current_offset
], "\xeb\x7e\xff\x00", 4) == 0))  
      {
 
         startup_header = (startup_header_t *) &filedata[current_offset];
 
 
 
         // layout:
 
         // [STARTUP HEADER]
 
         // (startup file blob)
 
         // [STARTUP TRAILER v1 or v2]
 
 
 
         printf ("Startup header at offset 0x%zx (%zd):\n", current_offset
, current_offset
);  
         printf ("   signature     = %02x %02x %02x %02x - good\n", startup_header
->signature
[0], startup_header
->signature
[1], startup_header
->signature
[2], startup_header
->signature
[3]);  
         printf ("   version       = 0x%04x (%d) - %s\n", startup_header
->version
, startup_header
->version
, (startup_header
->version 
== 1 ? "looks good" : "???"));  
         printf ("   flags1        = 0x%02x (%s)\n", startup_header
->flags1
, describe_uint8 
(startup_header
->flags1
, startupheader_flags1_strings
));  
         printf ("   flags2        = 0x%02x (%s) - %s\n", startup_header
->flags2
, BINARY 
(startup_header
->flags2
), (startup_header
->flags2 
== 0 ? "looks good" : "???"));  
         printf ("   header_size   = 0x%04x (%d) - %s\n", startup_header
->header_size
, startup_header
->header_size
, (startup_header
->header_size 
== sizeof (startup_header_t
) ? "looks good" : "BAD"));  
         printf ("   machine       = 0x%04x (%d) - %s\n", startup_header
->machine
, startup_header
->machine
, (startup_header
->machine 
== STARTUP_HDR_MACHINE_X86_64 
? "x86_64" : (startup_header
->machine 
== STARTUP_HDR_MACHINE_AARCH64 
? "aarch64" : "unknown")));  
         printf ("   startup_vaddr = 0x%08x (%d) - virtual address to transfer to after IPL is done\n", startup_header
->startup_vaddr
, startup_header
->startup_vaddr
);  
         printf ("   paddr_bias    = 0x%08x (%d) - value to add to physical addresses to get an indirectable pointer value\n", startup_header
->paddr_bias
, startup_header
->paddr_bias
);  
         printf ("   image_paddr   = 0x%08x (%d) - physical address of image\n", startup_header
->image_paddr
, startup_header
->image_paddr
);  
         printf ("   ram_paddr     = 0x%08x (%d) - physical address of RAM to copy image to (startup_size bytes copied)\n", startup_header
->ram_paddr
, startup_header
->ram_paddr
);  
         printf ("   ram_size      = 0x%08x (%d) - amount of RAM used by the startup program and executables in the fs\n", startup_header
->ram_size
, startup_header
->ram_size
);  
         printf ("   startup_size  = 0x%08x (%d) - size of startup (never compressed) - %s\n", startup_header
->startup_size
, startup_header
->startup_size
, (current_offset 
+ sizeof (image_header_t
) + startup_header
->startup_size 
+ (startup_header
->flags1 
& STARTUP_HDR_FLAGS1_TRAILER_V2 
? sizeof (image_trailer_v2_t
) : sizeof (image_trailer_v1_t
)) < filesize 
? "looks good" : "BAD (IFS file too short)"));  
         printf ("   stored_size   = 0x%08x (%d) - size of entire image - %s\n", startup_header
->stored_size
, startup_header
->stored_size
, (startup_header
->stored_size 
== startup_header
->ram_size 
? "looks good" : "???"));  
         printf ("   imagefs_paddr = 0x%08x (%d) - set by IPL when startup runs - %s\n", startup_header
->imagefs_paddr
, startup_header
->imagefs_paddr
, (startup_header
->imagefs_paddr 
== 0 ? "looks good" : "??? should be zero"));  
         printf ("   imagefs_size  = 0x%08x (%d) - size of uncompressed imagefs\n", startup_header
->imagefs_size
, startup_header
->imagefs_size
);  
         printf ("   preboot_size  = 0x%04x (%d) - size of loaded before header - %s\n", startup_header
->preboot_size
, startup_header
->preboot_size
, (startup_header
->preboot_size 
== current_offset 
? "looks good" : "???"));  
         printf ("   zero0         = 0x%04x (%d) - zeros - %s\n", startup_header
->zero0
, startup_header
->zero0
, (startup_header
->zero0 
== 0 ? "looks good" : "??? should be zero"));  
         printf ("   zero[0]       = 0x%08x (%d) - zeros - %s\n", startup_header
->zero
[0], startup_header
->zero
[0], (startup_header
->zero
[0] == 0 ? "looks good" : "??? should be zero"));  
         printf ("   addr_off      = 0x%016llx (%lld) - offset for startup_vaddr and [image|ram|imagefs]_paddr - %s\n", startup_header
->addr_off
, startup_header
->addr_off
, (startup_header
->addr_off 
== 0 ? "looks good" : "??? should be zero"));  
         hex_printf ((uint8_t *) &startup_header->info[0], sizeof (startup_header->info), "   info[48] =\n");
 
 
 
         // validate that the file can contain up to the startup trailer
 
         if (current_offset + startup_header->startup_size > filesize)
 
         {
 
            fprintf (stderr
, "WARNING: this IFS file is too short (startup trailer would be past end of file)\n");  
            break;
 
         }
 
 
 
         // check if this endianness is ours
 
         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
 
 
 
         // locate the right startup trailer at the right offset
 
         if (startup_header->flags1 & STARTUP_HDR_FLAGS1_TRAILER_V2)
 
         {
 
            startup_trailer_v2 = (startup_trailer_v2_t *) &filedata[current_offset + startup_header->startup_size - sizeof (startup_trailer_v2_t)];
 
            startupfile_blobsize = startup_header->startup_size - sizeof (startup_header_t) - sizeof (startup_trailer_v2_t);
 
         }
 
         else // old V1 trailer
 
         {
 
            startup_trailer_v1 = (startup_trailer_v1_t *) &filedata[current_offset + startup_header->startup_size - sizeof (startup_trailer_v1_t)];
 
            startupfile_blobsize = startup_header->startup_size - sizeof (startup_header_t) - sizeof (startup_trailer_v1_t);
 
         }
 
 
 
         current_offset += sizeof (startup_header_t); // jump over the startup header and reach the startup blob
 
         printf ("Startup blob at offset 0x%zx (%zd):\n", current_offset
, current_offset
);  
         printf ("   size 0x%zx (%zd) bytes\n", startupfile_blobsize
, startupfile_blobsize
);  
         printf ("   checksum %d\n", update_checksum 
(0, &filedata
[current_offset
], startupfile_blobsize
, is_foreign_endianness
));  
 
 
         current_offset += startupfile_blobsize; // jump over the startup blob and reach the startup trailer
 
         printf ("Startup trailer at offset 0x%zx (%zd) - version %d:\n", current_offset
, current_offset
, (startup_header
->flags1 
& STARTUP_HDR_FLAGS1_TRAILER_V2 
? 2 : 1));  
         if (startup_header->flags1 & STARTUP_HDR_FLAGS1_TRAILER_V2)
 
         {
 
            for (byte_index = 0; byte_index < SHA512_DIGEST_LENGTH; byte_index++)
 
               sprintf (&recorded_sha512
[2 * byte_index
], "%02x", startup_trailer_v2
->sha512
[byte_index
]);  
            strcpy (computed_sha512
, SHA512 
(startup_header
, (size_t) ((uint8_t *) startup_trailer_v2 
- (uint8_t *) startup_header
), NULL
));  
            recorded_checksum = startup_trailer_v2->cksum;
 
            computed_checksum = update_checksum (0, startup_header, sizeof (startup_header_t) + startupfile_blobsize + SHA512_DIGEST_LENGTH, is_foreign_endianness);
 
            printf ("    sha512 = %s - %s\n", recorded_sha512
, (strcasecmp 
(computed_sha512
, recorded_sha512
) == 0 ? "GOOD" : "BAD"));  
            printf ("    cksum = 0x%08x (%d) - %s\n", recorded_checksum
, recorded_checksum
, (computed_checksum 
== recorded_checksum 
? "GOOD" : "BAD"));  
            if (strcasecmp (computed_sha512, recorded_sha512) != 0)
 
               printf ("Computed SHA-512: %s\n", computed_sha512
);  
            if (computed_checksum != recorded_checksum)
 
               printf ("Computed cksum: 0x%08x (%d)\n", computed_checksum
, computed_checksum
);  
         }
 
         else // old v1 trailer
 
         {
 
            recorded_checksum = startup_trailer_v1->cksum;
 
            computed_checksum = update_checksum (0, startup_header, sizeof (startup_header) + startupfile_blobsize, is_foreign_endianness);
 
            printf ("    cksum = 0x%08x (%d) - %s\n", recorded_checksum
, recorded_checksum
, (computed_checksum 
== recorded_checksum 
? "GOOD" : "BAD"));  
            if (computed_checksum != recorded_checksum)
 
               printf ("Computed cksum: 0x%08x (%d)\n", computed_checksum
, computed_checksum
);  
         }
 
 
 
         current_offset += (startup_header->flags1 & STARTUP_HDR_FLAGS1_TRAILER_V2 ? sizeof (startup_trailer_v2_t) : sizeof (startup_trailer_v1_t)); // now reach the next segment
 
      }
 
 
 
      // else does an image header start here ?
 
      else if ((current_offset 
+ sizeof (image_header_t
) < filesize
) && (memcmp (&filedata
[current_offset
], "imagefs", 7) == 0))  
      {
 
         imageheader_offset = current_offset;
 
         image_header = (image_header_t *) &filedata[imageheader_offset];
 
 
 
         // layout:
 
         // [IMAGE HEADER]
 
         // [image directory entries]
 
         // [smallest file blobs up to KERNEL]
 
         // [padding]
 
         // [KERNEL]
 
         // [rest of file blobs]
 
         // [IMAGE FOOTER]
 
 
 
         printf ("Image header at offset %zx (%zd):\n", current_offset
, current_offset
);  
         printf ("   signature    = %02x %02x %02x %02x %02x %02x %02x (\"%.7s\") - good\n", image_header
->signature
[0], image_header
->signature
[1], image_header
->signature
[2], image_header
->signature
[3], image_header
->signature
[4], image_header
->signature
[5], image_header
->signature
[6], image_header
->signature
);  
         printf ("   flags        = 0x%02x (%s)\n", image_header
->flags
, describe_uint8 
(image_header
->flags
, imageheader_flags_strings
));  
         printf ("   image_size   = 0x%08x (%d) - size from header to end of trailer - %s\n", image_header
->image_size
, image_header
->image_size
, (current_offset 
+ image_header
->image_size 
<= filesize 
? "looks good" : "BAD (IFS file too short)"));  
         printf ("   hdr_dir_size = 0x%08x (%d) - size from header to last dirent - %s\n", image_header
->hdr_dir_size
, image_header
->hdr_dir_size
, (current_offset 
+ image_header
->hdr_dir_size 
< filesize 
? "looks good" : "BAD (IFS file too short)"));  
         printf ("   dir_offset   = 0x%08x (%d) - offset from header to first dirent - %s\n", image_header
->dir_offset
, image_header
->dir_offset
, (current_offset 
+ image_header
->dir_offset 
>= filesize 
? "BAD (IFS file too short)" : (image_header
->dir_offset 
> image_header
->hdr_dir_size 
? "BAD" : "looks good")));  
         printf ("   boot_ino[4]  = { 0x%08x, 0x%08x, 0x%08x, 0x%08x }\n", image_header
->boot_ino
[0], image_header
->boot_ino
[1], image_header
->boot_ino
[2], image_header
->boot_ino
[3]);  
         printf ("   script_ino   = 0x%08x (%d) - inode of compiled bootscript\n", image_header
->script_ino
, image_header
->script_ino
);  
         printf ("   chain_paddr  = 0x%08x (%d) - offset to next fs signature\n", image_header
->chain_paddr
, image_header
->chain_paddr
);  
         hex_printf ((uint8_t *) &image_header->spare[0], sizeof (image_header->spare), "   spare[10] =\n");
 
         printf ("   mountflags   = 0x%08x (%s %s %s %s)\n", image_header
->mountflags
, BINARY 
(((uint8_t *) &image_header
->mountflags
)[0]), BINARY 
(((uint8_t *) &image_header
->mountflags
)[1]), BINARY 
(((uint8_t *) &image_header
->mountflags
)[2]), BINARY 
(((uint8_t *) &image_header
->mountflags
)[3]));  
         printf ("   mountpoint   = \"%s\"\n", image_header
->mountpoint
);  
 
 
         // validate that the file can contain up to the image trailer
 
         if (current_offset + image_header->image_size > filesize)
 
         {
 
            fprintf (stderr
, "WARNING: this IFS file is too short (image trailer would be past end of file)\n");  
            break;
 
         }
 
 
 
         // check if this endianness is ours
 
         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
 
 
 
         // locate the image trailer at the right offset
 
         if (image_header->flags & IMAGE_FLAGS_TRAILER_V2)
 
         {
 
            imagetrailer_offset = current_offset + image_header->image_size - sizeof (image_trailer_v2_t);
 
            image_trailer_v2 = (image_trailer_v2_t *) &filedata[imagetrailer_offset];
 
         }
 
         else // old V1 trailer
 
         {
 
            imagetrailer_offset = current_offset + image_header->image_size - sizeof (image_trailer_v1_t);
 
            image_trailer_v1 = (image_trailer_v1_t *) &filedata[imagetrailer_offset];
 
         }
 
 
 
         current_offset += sizeof (image_header_t); // jump over the image header and reach the first directory entry
 
 
 
         // there may be padding before the first directory entry
 
         if (image_header->dir_offset - sizeof (image_header_t) > 0)
 
            hex_printf (&filedata[current_offset], image_header->dir_offset - sizeof (image_header_t), "\n" "%zd padding bytes at offset 0x%zd (%zd):\n", image_header->dir_offset - sizeof (image_header_t), current_offset, current_offset);
 
         current_offset += image_header->dir_offset - sizeof (image_header_t); // padding was processed, jump over it
 
 
 
         // dump all directory entries until the last one included
 
         fsentries = NULL;
 
         fsentry_count = 0;
 
         while (current_offset < imageheader_offset + image_header->hdr_dir_size)
 
         {
 
            current_fsentry = (fsentry_t *) &filedata[current_offset];
 
 
 
            if (imageheader_offset + image_header->hdr_dir_size - current_offset < sizeof (current_fsentry->header))
 
               break; // end padding reached
 
 
 
            // stack up the filesystem entry pointers in an array while we read them
 
            reallocated_ptr 
= realloc (fsentries
, (fsentry_count 
+ 1) * sizeof (fsentry_t 
*)); 
            if (reallocated_ptr == NULL)
 
            {
 
               fprintf (stderr
, "fatal error: out of memory\n");  
            }
 
            fsentries = reallocated_ptr;
 
            fsentries[fsentry_count] = current_fsentry;
 
            fsentry_count++;
 
 
 
            printf ("Filesystem entry at offset 0x%zx (%zd) - last one at 0x%zd (%zd):\n", current_offset
, current_offset
, imageheader_offset 
+ image_header
->hdr_dir_size
, imageheader_offset 
+ image_header
->hdr_dir_size
);  
            printf ("   size           = 0x%04x (%d) - size of dirent - %s\n", current_fsentry
->header.
size, current_fsentry
->header.
size, (current_offset 
+ current_fsentry
->header.
size < filesize 
? "looks good" : "BAD"));  
            printf ("   extattr_offset = 0x%04x (%d) - %s\n", current_fsentry
->header.
extattr_offset, current_fsentry
->header.
extattr_offset, (current_fsentry
->header.
extattr_offset == 0 ? "no extattr" : "has extattr"));  
            printf ("   ino            = 0x%08x (%d) - inode number (%s%s%s%s)\n", current_fsentry
->header.
ino, current_fsentry
->header.
ino, (current_fsentry
->header.
ino & 0xE0000000 ? "is" : "nothing special"), (current_fsentry
->header.
ino & IFS_INO_PROCESSED_ELF 
? " PROCESSED_ELF" : ""), (current_fsentry
->header.
ino & IFS_INO_RUNONCE_ELF 
? " RUNONCE_ELF" : ""), (current_fsentry
->header.
ino & IFS_INO_BOOTSTRAP_EXE 
? " BOOTSTRAP_EXE" : ""));  
            printf ("   mode           = 0x%08x (%d) - %s (0%o), POSIX permissions 0%o\n", current_fsentry
->header.
mode, current_fsentry
->header.
mode, (S_ISDIR 
(current_fsentry
->header.
mode) ? "directory" : (S_ISREG 
(current_fsentry
->header.
mode) ? "file" : (S_ISLNK 
(current_fsentry
->header.
mode) ? "symlink" : "device"))), (current_fsentry
->header.
mode & 0xF000) >> 12, current_fsentry
->header.
mode & 0xFFF);  
            printf ("   gid            = 0x%08x (%d) - owner group ID%s\n", current_fsentry
->header.
gid, current_fsentry
->header.
gid, (current_fsentry
->header.
gid == 0 ? " (root)" : ""));  
            printf ("   uid            = 0x%08x (%d) - owner user ID%s\n", current_fsentry
->header.
uid, current_fsentry
->header.
uid, (current_fsentry
->header.
uid == 0 ? " (root)" : ""));  
            mtime = (time_t) current_fsentry->header.mtime;
 
            printf ("   mtime          = 0x%08x (%d) - POSIX timestamp: %s", current_fsentry
->header.
mtime, current_fsentry
->header.
mtime, asctime (localtime (&mtime
))); // NOTE: asctime() provides the newline  
            if (S_ISDIR (current_fsentry->header.mode))
 
               printf ("   [DIRECTORY] path = \"%s\"\n", (char *) ¤t_fsentry
->u.
dir.
path); // convert from pointer to char array  
            else if (S_ISREG (current_fsentry->header.mode))
 
            {
 
               printf ("   [FILE] offset = 0x%08x (%d) - %s\n", current_fsentry
->u.
file.
offset, current_fsentry
->u.
file.
offset, (imageheader_offset 
+ current_fsentry
->u.
file.
offset < filesize 
? "looks good" : "BAD (IFS file too short)"));  
               printf ("   [FILE] size   = 0x%08x (%d) - %s\n", current_fsentry
->u.
file.
size, current_fsentry
->u.
file.
size, (imageheader_offset 
+ current_fsentry
->u.
file.
offset + current_fsentry
->u.
file.
size < filesize 
? "looks good" : "BAD (IFS file too short)"));  
               printf ("   [FILE] path   = \"%s\"\n", (char *) ¤t_fsentry
->u.
file.
path); // convert from pointer to char array  
            }
 
            else if (S_ISLNK (current_fsentry->header.mode))
 
            {
 
               printf ("   [SYMLINK] sym_offset = 0x%04x (%d) - %s\n", current_fsentry
->u.
symlink.
sym_offset, current_fsentry
->u.
symlink.
sym_offset, (sizeof (current_fsentry
->header
) + 2 * sizeof (uint16_t) + current_fsentry
->u.
symlink.
sym_offset <= current_fsentry
->header.
size ? "looks good" : "BAD (dirent too short)"));  
               printf ("   [SYMLINK] sym_size   = 0x%04x (%d) - %s\n", current_fsentry
->u.
symlink.
sym_size, current_fsentry
->u.
symlink.
sym_size, (sizeof (current_fsentry
->header
) + 2 * sizeof (uint16_t) + current_fsentry
->u.
symlink.
sym_offset + current_fsentry
->u.
symlink.
sym_size <= current_fsentry
->header.
size ? "looks good" : "BAD (dirent too short)"));  
               printf ("   [SYMLINK] path       = \"%s\"\n", (char *) ¤t_fsentry
->u.
symlink.
path); // convert from pointer to char array  
               printf ("   [SYMLINK] contents   = \"%s\"\n", ((char *) ¤t_fsentry
->u.
symlink.
path) + current_fsentry
->u.
symlink.
sym_offset); // convert from pointer to char array  
            }
 
            else // can only be a device
 
            {
 
               printf ("   [DEVICE] dev  = 0x%08x (%d)\n", current_fsentry
->u.
device.
dev, current_fsentry
->u.
device.
dev);  
               printf ("   [DEVICE] rdev = 0x%08x (%d)\n", current_fsentry
->u.
device.
rdev, current_fsentry
->u.
device.
rdev);  
               printf ("   [DEVICE] path = \"%s\"\n", (char *) ¤t_fsentry
->u.
device.
path); // convert from pointer to char array  
            }
 
 
 
            current_offset += current_fsentry->header.size;
 
         }
 
         if (imageheader_offset + image_header->hdr_dir_size < current_offset + sizeof (current_fsentry->header))
 
            hex_printf (&filedata[current_offset], imageheader_offset + image_header->hdr_dir_size - current_offset, "\n" "%zd padding bytes at offset 0x%zx (%zd):\n", imageheader_offset + image_header->hdr_dir_size - current_offset, current_offset, current_offset);
 
         current_offset += imageheader_offset + image_header->hdr_dir_size - current_offset; // padding was processed, jump over it
 
 
 
         // at this point we are past the directory entries; what is stored now, up to and until the image trailer, is the files' data
 
         if (fsentry_count > 0)
 
         {
 
            while (current_offset < imagetrailer_offset) // and parse data up to the trailer
 
            {
 
               nearest_distance = SIZE_MAX;
 
               nearest_index = SIZE_MAX;
 
               for (fsentry_index = 0; fsentry_index < fsentry_count; fsentry_index++)
 
                  if (S_ISREG (fsentries[fsentry_index]->header.mode) // if this directory entry a file (i.e. it has a data blob)...
 
                      && (imageheader_offset + (size_t) fsentries[fsentry_index]->u.file.offset >= current_offset) // ... AND its data blob is still ahead of our current pointer ...
 
                      && (imageheader_offset + (size_t) fsentries[fsentry_index]->u.file.offset - current_offset < nearest_distance)) // ... AND it's the closest to us we've found so far
 
                  {
 
                     nearest_distance = imageheader_offset + (size_t) fsentries[fsentry_index]->u.file.offset - current_offset; // then remember it
 
                     nearest_index = fsentry_index;
 
                  }
 
               if (nearest_index == SIZE_MAX)
 
                  break; // found no file ahead, which means we've parsed the whole file data area, so stop the loop so as to proceed to the image trailer
 
 
 
               fsentry_index = nearest_index;
 
               current_fsentry = fsentries[fsentry_index]; // quick access to closest fsentry
 
 
 
               // there may be padding before the file data
 
               if (imageheader_offset + (size_t) current_fsentry->u.file.offset - current_offset > 0)
 
                  hex_printf (&filedata[current_offset], imageheader_offset + (size_t) current_fsentry->u.file.offset - current_offset, "\n" "%zd padding bytes at offset 0x%zx (%zd):\n", imageheader_offset + (size_t) current_fsentry->u.file.offset - current_offset, current_offset, current_offset);
 
               current_offset += imageheader_offset + (size_t) current_fsentry->u.file.offset - current_offset; // padding was processed, jump over it
 
 
 
               printf ("File data blob at offset 0x%zx (%zd):\n", current_offset
, current_offset
);  
               printf ("   corresponding dirent index: %zd/%zd\n", fsentry_index
, fsentry_count
);  
               printf ("   corresponding inode 0x%08x (%d) -%s%s%s%s\n", current_fsentry
->header.
ino, current_fsentry
->header.
ino, (current_fsentry
->header.
ino & 0xE0000000 ? "" : " nothing special"), (current_fsentry
->header.
ino & IFS_INO_PROCESSED_ELF 
? " PROCESSED_ELF" : ""), (current_fsentry
->header.
ino & IFS_INO_RUNONCE_ELF 
? " RUNONCE_ELF" : ""), (current_fsentry
->header.
ino & IFS_INO_BOOTSTRAP_EXE 
? " BOOTSTRAP_EXE" : ""));  
               printf ("   corresponding path: \"%s\"\n", (char *) ¤t_fsentry
->u.
file.
path); // convert from pointer to char array  
               printf ("   size 0x%zx (%zd) bytes\n", (size_t) current_fsentry
->u.
file.
size, (size_t) current_fsentry
->u.
file.
size);  
               printf ("   first 4 bytes: %02x%02x%02x%02x \"%c%c%c%c\" (%s)\n", (uint8_t) filedata
[current_offset 
+ 0], (uint8_t) filedata
[current_offset 
+ 1], (uint8_t) filedata
[current_offset 
+ 2], (uint8_t) filedata
[current_offset 
+ 3], (isprint (filedata
[current_offset 
+ 0]) ? filedata
[current_offset 
+ 0] : '.'), (isprint (filedata
[current_offset 
+ 1]) ? filedata
[current_offset 
+ 1] : '.'), (isprint (filedata
[current_offset 
+ 2]) ? filedata
[current_offset 
+ 2] : '.'), (isprint (filedata
[current_offset 
+ 3]) ? filedata
[current_offset 
+ 3] : '.'), (memcmp (&filedata
[current_offset
], "\x7f" "ELF", 4) == 0 ? "ELF binary" : (memcmp (&filedata
[current_offset
], "#!", 2) == 0 ? "shell script" : "data file")));  
               printf ("   checksum %d\n", update_checksum 
(0, &filedata
[current_offset
], current_fsentry
->u.
file.
size, is_foreign_endianness
));  
 
 
               current_offset += current_fsentry->u.file.size; // now jump over this file's data
 
            }
 
         }
 
 
 
         // ad this point we're past the last file data, there may be padding before the image trailer
 
         if (imagetrailer_offset - current_offset > 0)
 
            hex_printf (&filedata[current_offset], imagetrailer_offset - current_offset, "\n" "%zd padding bytes at offset %zx (%zd):\n", imagetrailer_offset - current_offset, current_offset, current_offset);
 
         current_offset += imagetrailer_offset - current_offset; // padding was processed, jump over it
 
 
 
         printf ("Image trailer at offset 0x%zx (%zd) - version %d:\n", current_offset
, current_offset
, (image_header
->flags 
& IMAGE_FLAGS_TRAILER_V2 
? 2 : 1));  
         if (image_header->flags & IMAGE_FLAGS_TRAILER_V2)
 
         {
 
            for (byte_index = 0; byte_index < SHA512_DIGEST_LENGTH; byte_index++)
 
               sprintf (&recorded_sha512
[2 * byte_index
], "%02x", image_trailer_v2
->sha512
[byte_index
]);  
            strcpy (computed_sha512
, SHA512 
(image_header
, (size_t) ((uint8_t *) image_trailer_v2 
- (uint8_t *) image_header
), NULL
));  
            recorded_checksum = image_trailer_v2->cksum;
 
            computed_checksum = update_checksum (0, image_header, image_header->image_size - sizeof (image_trailer_v2_t) + SHA512_DIGEST_LENGTH, is_foreign_endianness);
 
            printf ("    sha512 = %s - %s\n", recorded_sha512
, (strcasecmp 
(computed_sha512
, recorded_sha512
) == 0 ? "GOOD" : "BAD"));  
            printf ("    cksum = 0x%08x (%d) - %s\n", recorded_checksum
, recorded_checksum
, (computed_checksum 
== recorded_checksum 
? "GOOD" : "BAD"));  
            if (strcasecmp (computed_sha512, recorded_sha512) != 0)
 
               printf ("Computed SHA-512: %s\n", computed_sha512
);  
            if (computed_checksum != recorded_checksum)
 
               printf ("Computed cksum: 0x%08x (%d)\n", computed_checksum
, computed_checksum
);  
         }
 
         else // old v1 trailer
 
         {
 
            recorded_checksum = image_trailer_v1->cksum;
 
            computed_checksum = update_checksum (0, image_header, image_header->image_size - sizeof (image_trailer_v1_t), is_foreign_endianness);
 
            printf ("    cksum = 0x%08x (%d) - %s\n", recorded_checksum
, recorded_checksum
, (computed_checksum 
== recorded_checksum 
? "GOOD" : "BAD"));  
            if (computed_checksum != recorded_checksum)
 
               printf ("Computed cksum: 0x%08x (%d)\n", computed_checksum
, computed_checksum
);  
         }
 
 
 
         current_offset += (image_header->flags & IMAGE_FLAGS_TRAILER_V2 ? sizeof (image_trailer_v2_t) : sizeof (image_trailer_v1_t)); // now reach the next segment (typically end of file)
 
      }
 
 
 
      // else it has to be a boot blob, of which we don't know the size, except that it has to fit in 0xffff bytes and be immediately followed by a startup header
 
      else
 
      {
 
         // so scan for the first startup header magic and version (which makes us 6 bytes to scan for, i.e. "\xeb\x7e\xff\x00" for the magic and "\x01\x00" (LSB) for the version 1)
 
         for (byte_index = current_offset; byte_index < filesize - 6; byte_index++)
 
            if (memcmp (&filedata
[byte_index
], "\xeb\x7e\xff\x00" "\x01\x00", 4 + 2) == 0)  
               break; // stop as soon as we find it
 
 
 
         if (byte_index >= filesize - 6)
 
            break; // if not found, stop scanning
 
 
 
         bootfile_blobsize = byte_index - current_offset;
 
         printf ("Boot blob at offset 0x%zx (%zd):\n", current_offset
, current_offset
);  
         printf ("   size 0x%zx (%zd) bytes\n", bootfile_blobsize
, bootfile_blobsize
);  
         printf ("   checksum 0x%08x\n", update_checksum 
(0, &filedata
[current_offset
], bootfile_blobsize
, false)); // NOTE: endianness is not known yet -- assume same  
 
 
         current_offset = byte_index; // now reach the next segment
 
      }
 
   }
 
 
 
   // at this point there's nothing left we're able to parse
 
   if (current_offset < filesize)
 
   {
 
      printf ("End of identifiable data reached.\n");  
      if (filesize - current_offset < 16384)
 
         hex_printf (&filedata[current_offset], filesize - current_offset, "\n" "%zd extra bytes at offset %zx (%zd):\n", filesize - current_offset, current_offset, current_offset);
 
      else
 
         printf ("\n" "%zd extra bytes at offset %zx (%zd) - size > 16k, not printed\n", filesize 
- current_offset
, current_offset
, current_offset
);  
   }
 
 
 
   printf ("End of file reached at offset 0x%zx (%zd)\n", filesize
, filesize
);  
   printf ("IFS dissecation complete.\n");  
   return (0);
 
}