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