Rev 16 | Rev 18 | Go to most recent revision | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed
Rev 16 | Rev 17 | ||
---|---|---|---|
Line 82... | Line 82... | ||
82 | int uid; // owner user ID (e.g. 0 = root) |
82 | int uid; // owner user ID (e.g. 0 = root) |
83 | int gid; // owner group ID (e.g. 0 = root) |
83 | int gid; // owner group ID (e.g. 0 = root) |
84 | int st_mode; // entry type (e.g. S_IFREG for files) and permissions |
84 | int st_mode; // entry type (e.g. S_IFREG for files) and permissions |
85 | uint32_t mtime; // entry's modification time POSIX timestamp - set to UINT32_MAX to use the concerned files' mtime on the build host |
85 | uint32_t mtime; // entry's modification time POSIX timestamp - set to UINT32_MAX to use the concerned files' mtime on the build host |
86 | uint32_t mtime_for_inline_files; // same as above but only for files that don't exist on the build host (i.e. files with an explicit content blob) |
86 | uint32_t mtime_for_inline_files; // same as above but only for files that don't exist on the build host (i.e. files with an explicit content blob) |
87 | char |
87 | char *prefix; // [prefix=path] install path (e.g. "proc/boot") |
88 | bool should_follow_symlinks; // follow symlinks |
88 | bool should_follow_symlinks; // follow symlinks |
89 | bool should_autosymlink_dylib; // dynamic libraries should be written under their official SONAME and a named symlink be created pointing at them |
89 | bool should_autosymlink_dylib; // dynamic libraries should be written under their official SONAME and a named symlink be created pointing at them |
90 | bool should_keep_ld_output; // whether to keep .sym files produced by ld calls, togglable by the [+keeplinked] attribute |
90 | bool should_keep_ld_output; // whether to keep .sym files produced by ld calls, togglable by the [+keeplinked] attribute |
91 | bool is_compiled_bootscript; // entry has [+script] attribute |
91 | bool is_compiled_bootscript; // entry has [+script] attribute |
92 | int extra_ino_flags; // bitmap of extra inode flags (IFS_INO_xxx) |
92 | int extra_ino_flags; // bitmap of extra inode flags (IFS_INO_xxx) |
93 | char |
93 | char *search; // [search=path[:path]] binary search path (the default one will be constructed at startup) |
94 | 94 | ||
95 | buffer_t data; // the resolved file's own data bytes |
95 | buffer_t data; // the resolved file's own data bytes |
96 | } parms_t; |
96 | } parms_t; |
97 | 97 | ||
98 | 98 | ||
Line 109... | Line 109... | ||
109 | static uint32_t image_align = 4; // default image alignment, as per QNX docs |
109 | static uint32_t image_align = 4; // default image alignment, as per QNX docs |
110 | static uint32_t image_kernel_ino = 0; |
110 | static uint32_t image_kernel_ino = 0; |
111 | static uint32_t image_bootscript_ino = 0; |
111 | static uint32_t image_bootscript_ino = 0; |
112 | #if defined(__x86_64__) |
112 | #if defined(__x86_64__) |
113 | static char image_processor[16] = "x86_64"; // default CPU type for which this image is built, either "x86_64" or "aarch64le" (will be used to find out the right include paths under $QNX_TARGET) |
113 | static char image_processor[16] = "x86_64"; // default CPU type for which this image is built, either "x86_64" or "aarch64le" (will be used to find out the right include paths under $QNX_TARGET) |
- | 114 | static char image_processor_base[16] = "x86_64"; // default base CPU type for which this image is built, either "x86_64" or "aarch64le" (will be used to find out the right include paths under $QNX_TARGET) |
|
114 | #elif defined(__aarch64__) |
115 | #elif defined(__aarch64__) |
115 | static char image_processor[16] = "aarch64le"; // default CPU type for which this image is built, either "x86_64" or "aarch64le" (will be used to find out the right include paths under $QNX_TARGET) |
116 | static char image_processor[16] = "aarch64le"; // default CPU type for which this image is built, either "x86_64" or "aarch64le" (will be used to find out the right include paths under $QNX_TARGET) |
- | 117 | static char image_processor_base[16] = "aarch64"; // default base CPU type for which this image is built, either "x86_64" or "aarch64le" (will be used to find out the right include paths under $QNX_TARGET) |
|
116 | #else // unknown platform |
118 | #else // unknown platform |
117 | #error Please port ifstool to this platform |
119 | #error Please port ifstool to this platform |
118 | #endif |
120 | #endif |
119 | static char *buildfile_pathname = NULL; // pathname of IFS build file |
121 | static char *buildfile_pathname = NULL; // pathname of IFS build file |
120 | static char *current_line = NULL; // copy of current line in IFS build file |
122 | static char *current_line = NULL; // copy of current line in IFS build file |
121 | static int lineno = 0; // current line number in IFS build file |
123 | static int lineno = 0; // current line number in IFS build file |
122 | static char *QNX_TARGET = NULL; // value of the $QNX_TARGET environment variable |
124 | static char *QNX_TARGET = NULL; // value of the $QNX_TARGET environment variable |
123 | static char * |
125 | static char *SEARCH_PATH = NULL; // mallocated string of search paths, populated by the -r command-line argument |
- | 126 | static char **saved_ELF_sections = NULL; // mallocated array of const strings, populated by the -s command-line argument |
|
- | 127 | static size_t saved_ELF_section_count = 0; // number of elements in the saved_ELF_sections array |
|
124 | 128 | ||
125 | // bootable IFS support |
129 | // bootable IFS support |
126 | static char *bootfile_pathname = NULL; // HACK: pathname to bootcode binary blob file to put at the start of a bootable IFS |
130 | static char *bootfile_pathname = NULL; // HACK: pathname to bootcode binary blob file to put at the start of a bootable IFS |
127 | static size_t bootfile_size = 0; // HACK: size of the bootcode binary blob file to put at the start of a bootable IFS |
131 | static size_t bootfile_size = 0; // HACK: size of the bootcode binary blob file to put at the start of a bootable IFS |
128 | static char *startupfile_pathname = NULL; // HACK: pathname to precompiled startup file blob to put in the startup header of a bootable IFS |
132 | static char *startupfile_pathname = NULL; // HACK: pathname to precompiled startup file blob to put in the startup header of a bootable IFS |
Line 135... | Line 139... | ||
135 | int32_t update_checksum (const void *data, const size_t data_len, const bool is_foreign_endianness); // compute an IFS image or startup checksum to store in the trailer |
139 | int32_t update_checksum (const void *data, const size_t data_len, const bool is_foreign_endianness); // compute an IFS image or startup checksum to store in the trailer |
136 | 140 | ||
137 | 141 | ||
138 | // prototypes of local functions |
142 | // prototypes of local functions |
139 | static long long read_integer (const char *str); // reads an integer number for a string that may be specified in either hex, octal or decimal base, and may have an optional unit suffix (k, m, g, t) |
143 | static long long read_integer (const char *str); // reads an integer number for a string that may be specified in either hex, octal or decimal base, and may have an optional unit suffix (k, m, g, t) |
140 | static char *resolve_pathname (const char *pathname, const char * |
144 | 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) |
141 | static elf_section_header_t *elf_get_section_header_by_name (const elf_header_t *elf, const char *section_name); // get a pointer to a named section header in an ELF file |
145 | static elf_section_header_t *elf_get_section_header_by_name (const elf_header_t *elf, const char *section_name); // get a pointer to a named section header in an ELF file |
142 | static size_t Buffer_WriteIFSDirectoryEntryAt (buffer_t *ifs_data, const size_t write_offset, const fsentry_t *fsentry); // writes the given filesystem entry (without its contents) to the IFS buffer |
146 | static size_t Buffer_WriteIFSDirectoryEntryAt (buffer_t *ifs_data, const size_t write_offset, const fsentry_t *fsentry); // writes the given filesystem entry (without its contents) to the IFS buffer |
143 | static size_t Buffer_AppendIFSFileData (buffer_t *ifs_data, fsentry_t *fsentry); // writes the given filesystem entry's file data (i.e. its contents) to the IFS buffer |
147 | static size_t Buffer_AppendIFSFileData (buffer_t *ifs_data, fsentry_t *fsentry); // writes the given filesystem entry's file data (i.e. its contents) to the IFS buffer |
144 | static int Buffer_StripELFFile (buffer_t *file, const char *indicative_pathname); // strips an ELF file buffer the way mkifs does it and returns whether it succeeded |
148 | static int Buffer_StripELFFile (buffer_t *file, const char **saved_sections, const size_t saved_section_count, const char *indicative_pathname); // strips an ELF file buffer the way mkifs does it and returns whether it succeeded |
145 | static size_t add_fsentry (fsentry_t **fsentries, size_t *fsentry_count, parms_t *entry_parms, const char *stored_pathname, const char *buildhost_pathname); // stack up a new filesystem entry |
149 | static size_t add_fsentry (fsentry_t **fsentries, size_t *fsentry_count, parms_t *entry_parms, const char *stored_pathname, const char *buildhost_pathname); // stack up a new filesystem entry |
146 | static int fsentry_compare_pathnames_cb (const void *a, const void *b); // qsort() comparison callback that sorts filesystem entries by pathnames |
150 | static int fsentry_compare_pathnames_cb (const void *a, const void *b); // qsort() comparison callback that sorts filesystem entries by pathnames |
147 | static void |
151 | 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 |
148 | 152 | ||
149 | 153 | ||
150 | // imported function prototypes |
154 | // imported function prototypes |
151 | extern int dump_ifs_info (const char *ifs_pathname, bool want_everything); // [implemented in ifsdump.c] dumps detailed info about a particular IFS file on the standard output, returns 0 on success and >0 on error |
155 | extern int dump_ifs_info (const char *ifs_pathname, bool want_everything); // [implemented in ifsdump.c] dumps detailed info about a particular IFS file on the standard output, returns 0 on success and >0 on error |
152 | extern int dump_ifs_contents (const char *ifs_pathname, const char *outdir); // [implemented in ifsdump.c] dumps the IFS filesystem contents in outdir, returns 0 on success and >0 on error |
156 | 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 |
Line 194... | Line 198... | ||
194 | } |
198 | } |
195 | return (ret); |
199 | return (ret); |
196 | } |
200 | } |
197 | 201 | ||
198 | 202 | ||
199 | static char *resolve_pathname (const char *pathname, const char * |
203 | static char *resolve_pathname (const char *pathname, const char *search_paths_or_NULL_for_MKIFS_PATH_envvar) |
200 | { |
204 | { |
201 | // locates pathname among search path and returns resolved pathname (static buffer) or NULL. |
205 | // locates pathname among search path and returns resolved pathname (static buffer) or NULL. |
202 | 206 | ||
- | 207 | typedef struct default_path_s { bool uses_processor_base; char *subpath; } default_path_t; |
|
- | 208 | ||
- | 209 | static const default_path_t default_paths[] = |
|
- | 210 | { |
|
- | 211 | { false, "/sbin" }, // prefix with $PROCESSOR/ |
|
- | 212 | { false, "/usr/sbin" }, // prefix with $PROCESSOR/ |
|
- | 213 | { false, "/boot/sys" }, // prefix with $PROCESSOR/ |
|
- | 214 | { true, "/boot/sys" }, // prefix with $PROCESSOR_BASE/ |
|
- | 215 | { false, "/bin" }, // prefix with $PROCESSOR/ |
|
- | 216 | { false, "/usr/bin" }, // prefix with $PROCESSOR/ |
|
- | 217 | { false, "/lib" }, // prefix with $PROCESSOR/ |
|
- | 218 | { false, "/lib/dll" }, // prefix with $PROCESSOR/ |
|
- | 219 | { false, "/usr/lib" } // prefix with $PROCESSOR/ |
|
- | 220 | }; |
|
203 | static thread_local char *resolved_pathname = NULL; |
221 | static thread_local char *resolved_pathname = NULL; |
204 | 222 | ||
- | 223 | size_t defaultpath_index; |
|
205 | struct stat stat_buf; |
224 | struct stat stat_buf; |
206 | const char *nextsep; |
225 | const char *nextsep; |
207 | const char *token; |
226 | const char *token; |
208 | 227 | ||
209 | // initial allocation (per thread) |
228 | // initial allocation (per thread) |
Line 212... | Line 231... | ||
212 | resolved_pathname = malloc (MAXPATHLEN); |
231 | resolved_pathname = malloc (MAXPATHLEN); |
213 | ASSERT_WITH_ERRNO (resolved_pathname); |
232 | ASSERT_WITH_ERRNO (resolved_pathname); |
214 | } |
233 | } |
215 | 234 | ||
216 | // is it an absolute pathname (POSIX and Windows variants) ? |
235 | // is it an absolute pathname (POSIX and Windows variants) ? |
- | 236 | if (IS_DIRSEP (pathname[0]) |
|
- | 237 | #ifdef _WIN32 |
|
217 |
|
238 | || (isalpha (pathname[0]) && (pathname[1] == ':') && IS_DIRSEP (pathname[2])) |
- | 239 | #endif // _WIN32 |
|
- | 240 | ) |
|
218 | strcpy_s (resolved_pathname, MAXPATHLEN, pathname); // in this case, it MUST exist at its designated location (either absolute or relative to the current working directory) |
241 | strcpy_s (resolved_pathname, MAXPATHLEN, pathname); // in this case, it MUST exist at its designated location (either absolute or relative to the current working directory) |
219 | else // the path is relative, search it among the search paths we have |
242 | else // the path is relative, search it among the search paths we have |
220 | { |
243 | { |
- | 244 | // QNX docs: |
|
- | 245 | // When searching for host files to be included in the image, search |
|
- | 246 | // the default paths used for storing binaries within the specified |
|
- | 247 | // directory before searching the default paths within $QNX_TARGET. |
|
- | 248 | // You can define multiple -r options; each adds a set of paths to |
|
- | 249 | // search for files. The -r options are evaluated from left to right |
|
- | 250 | // meaning the paths prefixed with the first (leftmost) rootdir are |
|
- | 251 | // searched first, then those prefixed with the second rootdir, and |
|
- | 252 | // so on. |
|
- | 253 | // Normally, mkifs searches any paths defined in $MKIFS_PATH when |
|
- | 254 | // it was called and then the default paths within $QNX_TARGET. The |
|
- | 255 | // default paths are based on the CPU architecture specified by |
|
- | 256 | // $PROCESSOR and $PROCESSOR_BASE. If you specify -r options, mkifs |
|
- | 257 | // searches the default paths prefixed with each dir variable before |
|
- | 258 | // searching those within $QNX_TARGET. These paths are: |
|
- | 259 | // dir/${PROCESSOR}/sbin |
|
- | 260 | // dir/${PROCESSOR}/usr/sbin |
|
- | 261 | // dir/${PROCESSOR}/boot/sys |
|
- | 262 | // dir/${PROCESSOR_BASE}/boot/sys |
|
- | 263 | // dir/${PROCESSOR}/bin |
|
- | 264 | // dir/${PROCESSOR}/usr/bin |
|
- | 265 | // dir/${PROCESSOR}/lib |
|
- | 266 | // dir/${PROCESSOR}/lib/dll |
|
- | 267 | // dir/${PROCESSOR}/usr/lib |
|
- | 268 | // NOTE: The structure of the directory paths under dir must be |
|
- | 269 | // identical to that of the default paths under $QNX_TARGET, but the |
|
- | 270 | // root dir itself may be any path you choose. For example, if you |
|
- | 271 | // wanted to include /scratch/aarch64le/sbin/devb-sata, you would |
|
- | 272 | // specify a -r option like this: |
|
- | 273 | // -r /scratch |
|
- | 274 | // Note that you don't include $PROCESSOR or $PROCESSOR_BASE in dir. |
|
- | 275 | ||
- | 276 | // - search all paths in explicit path/[default paths] (if explicit path supplied) |
|
- | 277 | // - search all paths in (-r flags if have some|MKIFS_PATH)/[default paths] (if no explicit path supplied) |
|
- | 278 | // - search all paths in $QNX_TARGET/[default paths] |
|
- | 279 | ||
- | 280 | // 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 |
|
- | 281 | if (search_paths_or_NULL_for_MKIFS_PATH_envvar == NULL) |
|
- | 282 | search_paths_or_NULL_for_MKIFS_PATH_envvar = (SEARCH_PATH != NULL ? SEARCH_PATH : getenv ("MKIFS_PATH")); |
|
- | 283 | ||
221 | // construct a potential final path using each element of the search path |
284 | // construct a potential final path using each element of the search path |
222 |
|
285 | if (search_paths_or_NULL_for_MKIFS_PATH_envvar != NULL) |
- | 286 | { |
|
- | 287 | token = (*search_paths_or_NULL_for_MKIFS_PATH_envvar != 0 ? search_paths_or_NULL_for_MKIFS_PATH_envvar : NULL); |
|
223 | nextsep = (token != NULL ? &token[strcspn (token, PATH_SEP)] : NULL); |
288 | nextsep = (token != NULL ? &token[strcspn (token, PATH_SEP)] : NULL); |
224 | while (token != NULL) |
289 | while (token != NULL) |
- | 290 | { |
|
- | 291 | for (defaultpath_index = 0; defaultpath_index < sizeof (default_paths) / sizeof (default_paths[0]); defaultpath_index++) |
|
- | 292 | { |
|
- | 293 | 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); |
|
- | 294 | if ((stat (resolved_pathname, &stat_buf) == 0) && S_ISREG (stat_buf.st_mode)) |
|
- | 295 | return (resolved_pathname); // if a file can indeed be found at this location, stop searching |
|
- | 296 | } |
|
- | 297 | ||
- | 298 | token = (*nextsep != 0 ? nextsep + 1 : NULL); |
|
- | 299 | nextsep = (token != NULL ? &token[strcspn (token, PATH_SEP)] : NULL); |
|
- | 300 | } |
|
- | 301 | } |
|
- | 302 | ||
- | 303 | // file not found in search paths: look under QNX_TARGET |
|
- | 304 | for (defaultpath_index = 0; defaultpath_index < sizeof (default_paths) / sizeof (default_paths[0]); defaultpath_index++) |
|
225 | { |
305 | { |
226 | sprintf_s (resolved_pathname, MAXPATHLEN, " |
306 | 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); |
227 | if ((stat (resolved_pathname, &stat_buf) == 0) && S_ISREG (stat_buf.st_mode)) |
307 | if ((stat (resolved_pathname, &stat_buf) == 0) && S_ISREG (stat_buf.st_mode)) |
228 | return (resolved_pathname); // if a file can indeed be found at this location, stop searching |
308 | return (resolved_pathname); // if a file can indeed be found at this location, stop searching |
229 | - | ||
230 | token = (*nextsep != 0 ? nextsep + 1 : NULL); |
- | |
231 | nextsep = (token != NULL ? &token[strcspn (token, PATH_SEP)] : NULL); |
- | |
232 | } |
309 | } |
233 | } |
310 | } |
234 | 311 | ||
235 | errno = ENOENT; // we exhausted all possibilities |
312 | errno = ENOENT; // we exhausted all possibilities |
236 | return (NULL); // file not found, return with ENOENT |
313 | return (NULL); // file not found, return with ENOENT |
Line 318... | Line 395... | ||
318 | { |
395 | { |
319 | // writes the given filesystem entry's file data (i.e. its contents) to the IFS buffer |
396 | // writes the given filesystem entry's file data (i.e. its contents) to the IFS buffer |
320 | 397 | ||
321 | elf_program_header_t *phdr; |
398 | elf_program_header_t *phdr; |
322 | elf_header_t *elf; |
399 | elf_header_t *elf; |
- | 400 | size_t fixed_physical_addr; |
|
323 | size_t corrective_offset; |
401 | size_t corrective_offset; |
324 | //size_t segment_type; |
402 | //size_t segment_type; |
325 | size_t segment_size; |
403 | size_t segment_size; |
326 | size_t table_index; |
404 | size_t table_index; |
327 | size_t table_count; |
405 | size_t table_count; |
Line 349... | Line 427... | ||
349 | 427 | ||
350 | 428 | ||
351 | corrective_offset = ELF_GET_NUMERIC (elf, phdr, virtual_addr) - ELF_GET_NUMERIC (elf, phdr, file_offset); |
429 | corrective_offset = ELF_GET_NUMERIC (elf, phdr, virtual_addr) - ELF_GET_NUMERIC (elf, phdr, file_offset); |
352 | segment_size = ELF_GET_NUMERIC (elf, phdr, size_in_memory); // get this ELF segment's occupied size in memory |
430 | segment_size = ELF_GET_NUMERIC (elf, phdr, size_in_memory); // get this ELF segment's occupied size in memory |
353 | if (segment_size != 0) // only patch the physical address of segments that have an actual size in memory |
431 | if (segment_size != 0) // only patch the physical address of segments that have an actual size in memory |
- | 432 | { |
|
- | 433 | fixed_physical_addr = ELF_GET_NUMERIC (elf, phdr, physical_addr) + image_base + data_offset - corrective_offset; |
|
354 | ELF_SET_NUMERIC (elf, phdr, physical_addr, |
434 | 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) |
- | 435 | } |
|
355 | } |
436 | } |
356 | } |
437 | } |
357 | 438 | ||
358 | ASSERT_WITH_ERRNO (Buffer_Append (ifs_data, fsentry->u.file.UNSAVED_databuf, fsentry->u.file.size)); // write file data blob |
439 | ASSERT_WITH_ERRNO (Buffer_Append (ifs_data, fsentry->u.file.UNSAVED_databuf, fsentry->u.file.size)); // write file data blob |
359 | return (ifs_data->size - data_offset); // return the number of bytes written |
440 | return (ifs_data->size - data_offset); // return the number of bytes written |
Line 374... | Line 455... | ||
374 | } |
455 | } |
375 | return (Buffer_OffsetOf (buffer, occurrence)); // can't fail |
456 | return (Buffer_OffsetOf (buffer, occurrence)); // can't fail |
376 | } |
457 | } |
377 | 458 | ||
378 | 459 | ||
379 | static int Buffer_StripELFFile (buffer_t *file, const char *indicative_pathname) |
460 | static int Buffer_StripELFFile (buffer_t *file, const char **saved_sections, const size_t saved_section_count, const char *indicative_pathname) |
380 | { |
461 | { |
381 | // NOTE: for each ELF file, mkifs |
462 | // NOTE: for each ELF file, mkifs |
382 | // -> alters the program header table and offsets each p_addr (physical address) member by <image_base> plus the current file offset (this cannot be done right now, will need to be done once they are known) |
463 | // -> alters the program header table and offsets each p_addr (physical address) member by <image_base> plus the current file offset (this cannot be done right now, will need to be done once they are known) |
383 | // -> throws away and reconstructs the sections table by keeping only the sections that are in the program header, and writes the section table at the start of the first thrown-away section |
464 | // -> throws away and reconstructs the sections table by keeping only the sections that are in the program header, and writes the section table at the start of the first thrown-away section |
384 | // FIXME: what if a thrown away section is located between two program segments ? are they collapsed, moving the segments beyond it one slot down ? |
465 | // FIXME: what if a thrown away section is located between two program segments ? are they collapsed, moving the segments beyond it one slot down ? |
Line 428... | Line 509... | ||
428 | { |
509 | { |
429 | const char *name; |
510 | const char *name; |
430 | elf_section_header_t header; |
511 | elf_section_header_t header; |
431 | buffer_t data; |
512 | buffer_t data; |
432 | } elf_section_t; |
513 | } elf_section_t; |
433 | - | ||
434 | static const char *saved_sections[] = { "QNX_info", ".gnu_debuglink", "QNX_usage", ".note.gnu.build-id" }; |
- | |
435 | 514 | ||
436 | const elf_program_header_t *phdr; |
515 | const elf_program_header_t *phdr; |
437 | const elf_section_header_t *shdr; |
516 | const elf_section_header_t *shdr; |
438 | elf_section_t *elf_sections = NULL; // mallocated |
517 | elf_section_t *elf_sections = NULL; // mallocated |
439 | elf_section_t *elf_section = NULL; |
518 | elf_section_t *elf_section = NULL; |
440 | size_t elf_section_count = 0; |
519 | size_t elf_section_count = 0; |
441 | size_t new_shdrtable_offset; |
520 | size_t new_shdrtable_offset; |
- | 521 | size_t new_shdrtable_len; |
|
442 | size_t sectiondata_start; |
522 | size_t sectiondata_start; |
443 | size_t sectiondata_size; |
523 | size_t sectiondata_size; |
444 | size_t array_index; |
524 | size_t array_index; |
445 | size_t table_index; |
525 | size_t table_index; |
446 | size_t table_count; |
526 | size_t table_count; |
Line 486... | Line 566... | ||
486 | ADD_SECTION (".shstrtab", &elf_section); // the first section will be the section names strings table |
566 | ADD_SECTION (".shstrtab", &elf_section); // the first section will be the section names strings table |
487 | ASSERT_WITH_ERRNO (Buffer_InitWithByteArray (&elf_section->data, "\0")); // initialize an empty section headers strings table |
567 | ASSERT_WITH_ERRNO (Buffer_InitWithByteArray (&elf_section->data, "\0")); // initialize an empty section headers strings table |
488 | ASSERT_WITH_ERRNO (Buffer_AppendByteArray (&elf_section->data, ".shstrtab\0")); // append ".shstrtab" *INCLUDING* its null terminator |
568 | ASSERT_WITH_ERRNO (Buffer_AppendByteArray (&elf_section->data, ".shstrtab\0")); // append ".shstrtab" *INCLUDING* its null terminator |
489 | 569 | ||
490 | // go through the saved sections array and see if such an ELF section is present in the ELF file |
570 | // go through the saved sections array and see if such an ELF section is present in the ELF file |
491 | for (array_index = 0; array_index < |
571 | for (array_index = 0; array_index < saved_section_count; array_index++) |
492 | if ((shdr = elf_get_section_header_by_name (ELFHDR, saved_sections[array_index])) != NULL) // does this ELF have such a section ? |
572 | if ((shdr = elf_get_section_header_by_name (ELFHDR, saved_sections[array_index])) != NULL) // does this ELF have such a section ? |
493 | { |
573 | { |
494 | ADD_SECTION (saved_sections[array_index], &elf_section); // yes, so save it |
574 | ADD_SECTION (saved_sections[array_index], &elf_section); // yes, so save it |
495 | sectiondata_start = ELF_GET_NUMERIC (ELFHDR, shdr, file_offset); // identify section data start offset |
575 | sectiondata_start = ELF_GET_NUMERIC (ELFHDR, shdr, file_offset); // identify section data start offset |
496 | sectiondata_size = ELF_GET_NUMERIC (ELFHDR, shdr, size); // identify section data length |
576 | sectiondata_size = ELF_GET_NUMERIC (ELFHDR, shdr, size); // identify section data length |
497 | if (sectiondata_start + sectiondata_size >= new_shdrtable_offset) // should this section be moved ? |
577 | if (sectiondata_start + sectiondata_size >= new_shdrtable_offset) // should this section be moved ? |
498 | ASSERT_WITH_ERRNO (Buffer_InitWithData (&elf_section->data, &file->bytes[sectiondata_start], sectiondata_size)); // have a copy of this section's data |
578 | ASSERT_WITH_ERRNO (Buffer_InitWithData (&elf_section->data, &file->bytes[sectiondata_start], sectiondata_size)); // have a copy of this section's data |
499 | else |
579 | else |
500 | Buffer_Initialize (&elf_section->data); // this section is located before the place where we'll write the new section headers table, thus it doesn't need to be moved |
580 | Buffer_Initialize (&elf_section->data); // this section is located before the place where we'll write the new section headers table, thus it doesn't need to be moved |
501 | //LOG_DEBUG ("%s: section '%s' start 0x%llx len 0x%llx", indicative_pathname, |
581 | //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); |
502 | 582 | ||
503 | // prepare this section's "fixed" header |
583 | // prepare this section's "fixed" header |
504 | memcpy (&elf_section->header, shdr, ELF_STRUCT_SIZE (ELFHDR, shdr)); // have a copy of the old section header first |
584 | memcpy (&elf_section->header, shdr, ELF_STRUCT_SIZE (ELFHDR, shdr)); // have a copy of the old section header first |
505 | ELF_SET_NUMERIC (ELFHDR, &elf_section->header, name_offset, Buffer_LocateOrAppendIfNecessaryAndReturnOffsetOf (&elf_sections[0].data, elf_section->name)); // make sure this section name is in the ELF sections section header strings table and update the relative offset of the section name |
585 | ELF_SET_NUMERIC (ELFHDR, &elf_section->header, name_offset, Buffer_LocateOrAppendIfNecessaryAndReturnOffsetOf (&elf_sections[0].data, elf_section->name)); // make sure this section name is in the ELF sections section header strings table and update the relative offset of the section name |
506 | } |
586 | } |
Line 535... | Line 615... | ||
535 | for (table_index = 1; table_index < elf_section_count; table_index++) |
615 | for (table_index = 1; table_index < elf_section_count; table_index++) |
536 | Buffer_WriteAt (file, new_shdrtable_offset + table_index * ELF_STRUCT_SIZE (ELFHDR, &elf_sections[table_index].header), &elf_sections[table_index].header, ELF_STRUCT_SIZE (ELFHDR, &elf_sections[table_index].header)); // write each section header |
616 | Buffer_WriteAt (file, new_shdrtable_offset + table_index * ELF_STRUCT_SIZE (ELFHDR, &elf_sections[table_index].header), &elf_sections[table_index].header, ELF_STRUCT_SIZE (ELFHDR, &elf_sections[table_index].header)); // write each section header |
537 | Buffer_WriteAt (file, new_shdrtable_offset + table_index * ELF_STRUCT_SIZE (ELFHDR, &elf_sections[table_index].header), &elf_sections[0].header, ELF_STRUCT_SIZE (ELFHDR, &elf_sections[0].header)); // write the section header names section header last |
617 | Buffer_WriteAt (file, new_shdrtable_offset + table_index * ELF_STRUCT_SIZE (ELFHDR, &elf_sections[table_index].header), &elf_sections[0].header, ELF_STRUCT_SIZE (ELFHDR, &elf_sections[0].header)); // write the section header names section header last |
538 | 618 | ||
539 | // and finally fix the ELF master header |
619 | // and finally fix the ELF master header |
- | 620 | new_shdrtable_len = 1 + elf_section_count; // take in account that the first entry in the section headers table is empty |
|
540 | ELF_SET_NUMERIC (ELFHDR, ELFHDR, section_header_table_offset, new_shdrtable_offset); |
621 | ELF_SET_NUMERIC (ELFHDR, ELFHDR, section_header_table_offset, new_shdrtable_offset); |
541 | ELF_SET_NUMERIC (ELFHDR, ELFHDR, section_header_table_len, |
622 | ELF_SET_NUMERIC (ELFHDR, ELFHDR, section_header_table_len, new_shdrtable_len); |
542 | ELF_SET_NUMERIC (ELFHDR, ELFHDR, section_header_names_idx, elf_section_count); // the section headers strings table is the last section |
623 | ELF_SET_NUMERIC (ELFHDR, ELFHDR, section_header_names_idx, elf_section_count); // the section headers strings table is the last section |
543 | 624 | ||
544 | // align size with page size (4096 on x86, 16k on ARM), zerofilling the extra space |
625 | // align size with page size (4096 on x86, 16k on ARM), zerofilling the extra space |
545 | ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (file, ROUND_TO_UPPER_MULTIPLE (file->size, page_size))); |
626 | ASSERT_WITH_ERRNO (Buffer_PadWithZeroesTo (file, ROUND_TO_UPPER_MULTIPLE (file->size, page_size))); |
546 | 627 | ||
Line 637... | Line 718... | ||
637 | // now parse the attribute tokens (NOTE: THE LIST OF ALLOWED ATTRIBUTES HERE IS NOT DOCUMENTED) |
718 | // now parse the attribute tokens (NOTE: THE LIST OF ALLOWED ATTRIBUTES HERE IS NOT DOCUMENTED) |
638 | token = strtok_r (linebit_start, RECORD_SEP, &ctx); |
719 | token = strtok_r (linebit_start, RECORD_SEP, &ctx); |
639 | while (token != NULL) |
720 | while (token != NULL) |
640 | { |
721 | { |
641 | #define REACH_TOKEN_VALUE() do { value = strchr (token, '=') + 1; if (*value == '"') value++; } while (0) |
722 | #define REACH_TOKEN_VALUE() do { value = strchr (token, '=') + 1; if (*value == '"') value++; } while (0) |
- | 723 | if (false) |
|
- | 724 | 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.) |
|
642 | if |
725 | else if (strncmp (token, "uid=", 4) == 0) { REACH_TOKEN_VALUE (); entry_parms->uid = (int) read_integer (value); } |
643 | else if (strncmp (token, "gid=", 4) == 0) { REACH_TOKEN_VALUE (); entry_parms->gid = (int) read_integer (value); } |
726 | else if (strncmp (token, "gid=", 4) == 0) { REACH_TOKEN_VALUE (); entry_parms->gid = (int) read_integer (value); } |
644 | else if (strncmp (token, "perms=", 6) == 0) { REACH_TOKEN_VALUE (); entry_parms->perms = (int) read_integer (value); } |
727 | else if (strncmp (token, "perms=", 6) == 0) { REACH_TOKEN_VALUE (); entry_parms->perms = (int) read_integer (value); } |
645 | else if (strncmp (token, "prefix=", 7) == 0) { REACH_TOKEN_VALUE (); strcpy (entry_parms->prefix, (*value == '/' ? value + 1 : value)); } // skip possible leading slash in prefix |
- | |
646 | else if (strcmp (token, "+followlink") == 0) entry_parms->should_follow_symlinks = true; |
728 | else if (strcmp (token, "+followlink") == 0) entry_parms->should_follow_symlinks = true; |
647 | else if (strcmp (token, "-followlink") == 0) entry_parms->should_follow_symlinks = false; |
729 | else if (strcmp (token, "-followlink") == 0) entry_parms->should_follow_symlinks = false; |
648 | else if (strcmp (token, "+keeplinked") == 0) entry_parms->should_keep_ld_output = true; |
730 | else if (strcmp (token, "+keeplinked") == 0) entry_parms->should_keep_ld_output = true; |
649 | else if (strcmp (token, "-keeplinked") == 0) entry_parms->should_keep_ld_output = false; |
731 | else if (strcmp (token, "-keeplinked") == 0) entry_parms->should_keep_ld_output = false; |
650 | else LOG_WARNING ("unimplemented bootstrap executable attribute in \"%s\" line %d: '%s'", buildfile_pathname, lineno, token); |
732 | else LOG_WARNING ("unimplemented bootstrap executable attribute in \"%s\" line %d: '%s'", buildfile_pathname, lineno, token); |
Line 763... | Line 845... | ||
763 | 845 | ||
764 | // now we know which startup and procnto executables to use |
846 | // now we know which startup and procnto executables to use |
765 | LOG_DEBUG ("Startup: %s", startup_name); |
847 | LOG_DEBUG ("Startup: %s", startup_name); |
766 | LOG_DEBUG ("Kernel: %s", procnto_name); |
848 | LOG_DEBUG ("Kernel: %s", procnto_name); |
767 | 849 | ||
768 | sprintf (candidate_pathname, "%s/%s", entry_parms->prefix, procnto_name); // fix the entry name |
850 | sprintf (candidate_pathname, "%s/%s", (entry_parms->prefix != NULL ? entry_parms->prefix : ""), procnto_name); // fix the entry name |
769 | stored_pathname = candidate_pathname; |
851 | stored_pathname = candidate_pathname; |
770 | entry_parms->extra_ino_flags |= /*IFS_INO_PROCESSED_ELF | */IFS_INO_BOOTSTRAP_EXE; // procnto needs to have these flags stamped on the inode |
852 | entry_parms->extra_ino_flags |= /*IFS_INO_PROCESSED_ELF | */IFS_INO_BOOTSTRAP_EXE; // procnto needs to have these flags stamped on the inode |
771 | entry_parms->st_mode = S_IFREG | entry_parms->perms; // apply specified procnto permissions |
853 | entry_parms->st_mode = S_IFREG | entry_parms->perms; // apply specified procnto permissions |
772 | image_kernel_ino = entry_parms->extra_ino_flags | (inode_count + 1); |
854 | image_kernel_ino = entry_parms->extra_ino_flags | (inode_count + 1); |
773 | 855 | ||
Line 789... | Line 871... | ||
789 | #endif |
871 | #endif |
790 | ASSERT (access (linker_pathname, 0) == 0, "host cross-linker for QNX8 \"%s\" not found", linker_pathname); |
872 | ASSERT (access (linker_pathname, 0) == 0, "host cross-linker for QNX8 \"%s\" not found", linker_pathname); |
791 | sprintf (linker_sysroot_arg, "--sysroot=%s/%s/", QNX_TARGET, image_processor); |
873 | sprintf (linker_sysroot_arg, "--sysroot=%s/%s/", QNX_TARGET, image_processor); |
792 | sprintf (linker_script_pathname_arg, "-T%s/%s/lib/nto.link", QNX_TARGET, image_processor); |
874 | sprintf (linker_script_pathname_arg, "-T%s/%s/lib/nto.link", QNX_TARGET, image_processor); |
793 | 875 | ||
794 | resolved_pathname = resolve_pathname (procnto_name, |
876 | resolved_pathname = resolve_pathname (procnto_name, entry_parms->search); // locate the procnto kernel location |
795 | ASSERT (resolved_pathname, "QNX kernel \"%s\" not found in search path", procnto_name); |
877 | ASSERT (resolved_pathname, "QNX kernel \"%s\" not found in search path", procnto_name); |
796 | strcpy (procnto_buildhost_pathname, resolved_pathname); |
878 | strcpy (procnto_buildhost_pathname, resolved_pathname); |
797 | 879 | ||
798 | sprintf (procnto_sym_filename, "%s.sym", procnto_name); |
880 | sprintf (procnto_sym_filename, "%s.sym", procnto_name); |
799 | 881 | ||
Line 820... | Line 902... | ||
820 | _spawnv (_P_WAIT, linker_pathname, linker_argv); // spawn the linker and produce a stripped procnto (wait for completion) |
902 | _spawnv (_P_WAIT, linker_pathname, linker_argv); // spawn the linker and produce a stripped procnto (wait for completion) |
821 | if (!Buffer_ReadFromFile (&entry_parms->data, procnto_sym_filename)) // load the output file |
903 | if (!Buffer_ReadFromFile (&entry_parms->data, procnto_sym_filename)) // load the output file |
822 | DIE_WITH_EXITCODE (1, "the host cross-linker failed to produce a readable stripped \"%s\" kernel: %s", procnto_sym_filename, strerror (errno)); |
904 | DIE_WITH_EXITCODE (1, "the host cross-linker failed to produce a readable stripped \"%s\" kernel: %s", procnto_sym_filename, strerror (errno)); |
823 | if (!entry_parms->should_keep_ld_output) |
905 | if (!entry_parms->should_keep_ld_output) |
824 | unlink (procnto_sym_filename); // remove the linker output file if we want to |
906 | unlink (procnto_sym_filename); // remove the linker output file if we want to |
- | 907 | ||
- | 908 | // now strip this prelinked ELF kernel file |
|
- | 909 | Buffer_StripELFFile (&entry_parms->data, saved_ELF_sections, 1, stored_pathname); // strip the ELF file as per QNX docs (only keep ONE section, which is "QNX_info") |
|
- | 910 | entry_parms->extra_ino_flags |= IFS_INO_PROCESSED_ELF; // mark this inode as a preprocessed ELF file |
|
- | 911 | ||
825 | #else // !PROCNTO_WIP |
912 | #else // !PROCNTO_WIP |
826 | /* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK */ |
913 | /* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK */ |
827 | /* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK */ |
914 | /* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK *//* HACK */ |
828 | /* HACK */ |
915 | /* HACK */ |
829 | /* HACK */ sprintf_s (candidate_pathname, MAXPATHLEN, "%s/procnto-smp-instr", entry_parms->prefix); // HACK: fix the entry name |
916 | /* HACK */ sprintf_s (candidate_pathname, MAXPATHLEN, "%s/procnto-smp-instr", (entry_parms->prefix != NULL ? entry_parms->prefix : "")); // HACK: fix the entry name |
830 | /* HACK */ stored_pathname = candidate_pathname; |
917 | /* HACK */ stored_pathname = candidate_pathname; |
831 | /* HACK */ entry_parms->extra_ino_flags |= IFS_INO_PROCESSED_ELF | IFS_INO_BOOTSTRAP_EXE; // procnto needs to have these flags stamped on the inode |
918 | /* HACK */ entry_parms->extra_ino_flags |= IFS_INO_PROCESSED_ELF | IFS_INO_BOOTSTRAP_EXE; // procnto needs to have these flags stamped on the inode |
832 | /* HACK */ entry_parms->st_mode = S_IFREG | 0700; // procnto requires 0700 permissions |
919 | /* HACK */ entry_parms->st_mode = S_IFREG | 0700; // procnto requires 0700 permissions |
833 | /* HACK */ image_kernel_ino = entry_parms->extra_ino_flags | (inode_count + 1); |
920 | /* HACK */ image_kernel_ino = entry_parms->extra_ino_flags | (inode_count + 1); |
834 | /* HACK */ free (entry_parms->data.bytes); // discard inline contents |
921 | /* HACK */ free (entry_parms->data.bytes); // discard inline contents |
Line 852... | Line 939... | ||
852 | entry_parms->mtime = entry_parms->mtime_for_inline_files; // if so, set it a mtime equal to the mtime to use for inline files |
939 | entry_parms->mtime = entry_parms->mtime_for_inline_files; // if so, set it a mtime equal to the mtime to use for inline files |
853 | LOG_INFO ("file: ino 0x%x uid %d gid %d mode 0%o path \"%s\" blob (len %zd)", entry_parms->extra_ino_flags | (inode_count + 1), entry_parms->uid, entry_parms->gid, entry_parms->st_mode, stored_pathname, entry_parms->data.size); |
940 | LOG_INFO ("file: ino 0x%x uid %d gid %d mode 0%o path \"%s\" blob (len %zd)", entry_parms->extra_ino_flags | (inode_count + 1), entry_parms->uid, entry_parms->gid, entry_parms->st_mode, stored_pathname, entry_parms->data.size); |
854 | } |
941 | } |
855 | else if (buildhost_pathname != NULL) // else was a source file pathname supplied ? |
942 | else if (buildhost_pathname != NULL) // else was a source file pathname supplied ? |
856 | { |
943 | { |
857 | resolved_pathname = resolve_pathname (buildhost_pathname, |
944 | resolved_pathname = resolve_pathname (buildhost_pathname, entry_parms->search); // locate the file |
858 | if (resolved_pathname == NULL) |
945 | if (resolved_pathname == NULL) |
859 | DIE_WITH_EXITCODE (1, "filesystem entry \"%s\" specified in \"%s\" line %d not found on build host: %s", buildhost_pathname, buildfile_pathname, lineno, strerror (errno)); |
946 | DIE_WITH_EXITCODE (1, "filesystem entry \"%s\" specified in \"%s\" line %d not found on build host: %s", buildhost_pathname, buildfile_pathname, lineno, strerror (errno)); |
860 | if (!Buffer_ReadFromFile (&entry_parms->data, resolved_pathname)) |
947 | if (!Buffer_ReadFromFile (&entry_parms->data, resolved_pathname)) |
861 | DIE_WITH_EXITCODE (1, "filesystem entry \"%s\" specified in \"%s\" line %d can't be read: %s", buildhost_pathname, buildfile_pathname, lineno, strerror (errno)); |
948 | DIE_WITH_EXITCODE (1, "filesystem entry \"%s\" specified in \"%s\" line %d can't be read: %s", buildhost_pathname, buildfile_pathname, lineno, strerror (errno)); |
862 | stat (resolved_pathname, &stat_buf); // can't fail, since we could read it |
949 | stat (resolved_pathname, &stat_buf); // can't fail, since we could read it |
Line 894... | Line 981... | ||
894 | } |
981 | } |
895 | 982 | ||
896 | // do we have it ? |
983 | // do we have it ? |
897 | if ((canonical_dylib_name != NULL) && (canonical_dylib_name[0] != 0)) |
984 | if ((canonical_dylib_name != NULL) && (canonical_dylib_name[0] != 0)) |
898 | { |
985 | { |
899 | sprintf_s (candidate_pathname, MAXPATHLEN, "%s/%s", entry_parms->prefix, canonical_dylib_name); |
986 | sprintf_s (candidate_pathname, MAXPATHLEN, "%s/%s", (entry_parms->prefix != NULL ? entry_parms->prefix : ""), canonical_dylib_name); |
900 | if (strcmp (candidate_pathname, stored_pathname) != 0) // claimed dylib name differs from passed name ? |
987 | if (strcmp (candidate_pathname, stored_pathname) != 0) // claimed dylib name differs from passed name ? |
901 | { |
988 | { |
902 | original_stored_pathname = stored_pathname; // if so, remember to create a symlink here |
989 | original_stored_pathname = stored_pathname; // if so, remember to create a symlink here |
903 | stored_pathname = candidate_pathname; |
990 | stored_pathname = candidate_pathname; |
904 | } |
991 | } |
Line 907... | Line 994... | ||
907 | } // end if the file we're storing is a dylib |
994 | } // end if the file we're storing is a dylib |
908 | 995 | ||
909 | // now strip this ELF file if necessary |
996 | // now strip this ELF file if necessary |
910 | if (!(entry_parms->extra_ino_flags & IFS_INO_PROCESSED_ELF)) |
997 | if (!(entry_parms->extra_ino_flags & IFS_INO_PROCESSED_ELF)) |
911 | { |
998 | { |
912 | Buffer_StripELFFile (&entry_parms->data, stored_pathname); // strip the ELF file à la mkifs |
999 | Buffer_StripELFFile (&entry_parms->data, saved_ELF_sections, saved_ELF_section_count, stored_pathname); // strip the ELF file à la mkifs |
913 | entry_parms->extra_ino_flags |= IFS_INO_PROCESSED_ELF; // mark this inode as a preprocessed ELF file |
1000 | entry_parms->extra_ino_flags |= IFS_INO_PROCESSED_ELF; // mark this inode as a preprocessed ELF file |
914 | } // end if the file is not yet a processed ELF |
1001 | } // end if the file is not yet a processed ELF |
915 | } // end if the file we're storing is an ELF file |
1002 | } // end if the file we're storing is an ELF file |
916 | #undef ELFHDR // undefine the macro that used to always point to the ELF header at the beginning of the file |
1003 | #undef ELFHDR // undefine the macro that used to always point to the ELF header at the beginning of the file |
917 | } |
1004 | } |
Line 1006... | Line 1093... | ||
1006 | const char *pathname_b = (S_ISDIR (entry_b->header.mode) ? entry_b->u.dir.path : (S_ISREG (entry_b->header.mode) ? entry_b->u.file.path : (S_ISLNK (entry_b->header.mode) ? entry_b->u.symlink.path : entry_b->u.device.path))); |
1093 | const char *pathname_b = (S_ISDIR (entry_b->header.mode) ? entry_b->u.dir.path : (S_ISREG (entry_b->header.mode) ? entry_b->u.file.path : (S_ISLNK (entry_b->header.mode) ? entry_b->u.symlink.path : entry_b->u.device.path))); |
1007 | return (strcmp (pathname_a, pathname_b)); |
1094 | return (strcmp (pathname_a, pathname_b)); |
1008 | } |
1095 | } |
1009 | 1096 | ||
1010 | 1097 | ||
1011 | static void |
1098 | static void parse_line (FILE *buildfile_fp, char *line_buffer, fsentry_t **fsentries, size_t *fsentry_count, parms_t *default_parms) |
1012 | { |
1099 | { |
- | 1100 | thread_local static char path_on_buildhost[MAXPATHLEN] = ""; |
|
- | 1101 | thread_local static char path_in_ifs[MAXPATHLEN] = ""; |
|
1013 | // |
1102 | 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) |
1014 | 1103 | ||
- | 1104 | bool should_discard_inline_contents; |
|
- | 1105 | bool is_quoted_context; |
|
- | 1106 | bool is_escaped_char; |
|
- | 1107 | struct stat stat_buf; |
|
- | 1108 | struct tm utc_time; |
|
1015 |
|
1109 | void *reallocated_ptr; |
- | 1110 | size_t allocated_size; |
|
1016 | size_t |
1111 | size_t string_len; |
- | 1112 | char *specifiedpathname_start; |
|
- | 1113 | char *attrblock_start; |
|
- | 1114 | char *write_ptr; |
|
1017 | char * |
1115 | char *line_ptr; |
- | 1116 | char *value; |
|
1018 | char *token; |
1117 | char *token; |
- | 1118 | char *sep; |
|
- | 1119 | char *ctx; |
|
- | 1120 | int read_char; |
|
1019 | 1121 | ||
- | 1122 | line_ptr = line_buffer; |
|
- | 1123 | while ((*line_ptr != 0) && isspace (*line_ptr)) |
|
- | 1124 | line_ptr++; // skip leading spaces |
|
- | 1125 | ||
- | 1126 | if ((*line_ptr == 0) || (*line_ptr == '#')) |
|
- | 1127 | return; // skip empty or comment lines |
|
- | 1128 | ||
- | 1129 | string_len = (int) strlen (line_buffer); |
|
- | 1130 | if ((string_len > 0) && (line_buffer[string_len - 1] == '\n')) |
|
1020 |
|
1131 | line_buffer[string_len - 1] = 0; // chop off newline for easier debug output |
- | 1132 | ||
1021 |
|
1133 | // reset entry values |
1022 |
|
1134 | memcpy (&entry_parms, default_parms, sizeof (parms_t)); |
- | 1135 | path_in_ifs[0] = 0; |
|
- | 1136 | path_on_buildhost[0] = 0; |
|
- | 1137 | should_discard_inline_contents = false; |
|
- | 1138 | ||
- | 1139 | // does this line start with an attribute block ? |
|
1023 |
|
1140 | if (*line_ptr == '[') |
1024 | { |
1141 | { |
- | 1142 | line_ptr++; // skip the leading square bracket |
|
- | 1143 | attrblock_start = line_ptr; // remember where it starts |
|
- | 1144 | is_quoted_context = false; |
|
- | 1145 | while ((*line_ptr != 0) && !((*line_ptr == ']') && (line_ptr[-1] != '\\') && !is_quoted_context)) |
|
- | 1146 | { |
|
- | 1147 | if (*line_ptr == '"') |
|
- | 1148 | is_quoted_context ^= true; // remember when we're between quotes |
|
- | 1149 | else if (!is_quoted_context && (*line_ptr == ' ')) |
|
- | 1150 | *line_ptr = RECORD_SEP[0]; // turn all spaces outside quoted contexts into an ASCII record separator to ease token splitting |
|
- | 1151 | line_ptr++; // reach the next unescaped closing square bracket |
|
- | 1152 | } |
|
1025 | if ( |
1153 | if (*line_ptr != ']') |
- | 1154 | { |
|
- | 1155 | LOG ("warning", 0, "syntax error in \"%s\" line %d: unterminated attributes block (skipping)", buildfile_pathname, lineno); |
|
- | 1156 | return; // invalid attribute block, skip line |
|
- | 1157 | } |
|
1026 |
|
1158 | *line_ptr = 0; // end the attribute block so that it is a parsable C string |
1027 | 1159 | ||
- | 1160 | // now parse the attribute tokens |
|
1028 |
|
1161 | // DOCUMENTATION: https://www.qnx.com/developers/docs/8.0/com.qnx.doc.neutrino.utilities/topic/m/mkifs.html#mkifs__description |
1029 | token = |
1162 | token = strtok_r (attrblock_start, RECORD_SEP, &ctx); |
1030 |
|
1163 | while (token != NULL) |
- | 1164 | { |
|
- | 1165 | // evaluate attribute token |
|
- | 1166 | #define REACH_TOKEN_VALUE() do { value = strchr (token, '=') + 1; if (*value == '"') value++; } while (0) |
|
- | 1167 | if (false) {} |
|
- | 1168 | 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.) |
|
- | 1169 | else if (strncmp (token, "uid=", 4) == 0) { REACH_TOKEN_VALUE (); entry_parms.uid = (int) read_integer (value); } |
|
- | 1170 | else if (strncmp (token, "gid=", 4) == 0) { REACH_TOKEN_VALUE (); entry_parms.gid = (int) read_integer (value); } |
|
- | 1171 | else if (strncmp (token, "dperms=", 7) == 0) { REACH_TOKEN_VALUE (); entry_parms.dperms = (int) read_integer (value); } |
|
- | 1172 | else if (strncmp (token, "perms=", 6) == 0) { REACH_TOKEN_VALUE (); entry_parms.perms = (int) read_integer (value); } |
|
- | 1173 | else if (strncmp (token, "type=", 5) == 0) { REACH_TOKEN_VALUE (); |
|
- | 1174 | if (strcmp (value, "dir") == 0) entry_parms.st_mode = S_IFDIR; |
|
- | 1175 | else if (strcmp (value, "file") == 0) entry_parms.st_mode = S_IFREG; |
|
- | 1176 | else if (strcmp (value, "link") == 0) entry_parms.st_mode = S_IFLNK; |
|
- | 1177 | else if (strcmp (value, "fifo") == 0) entry_parms.st_mode = S_IFIFO; |
|
- | 1178 | else DIE_WITH_EXITCODE (1, "invalid 'type' attribute in \"%s\" line %d: '%s'", buildfile_pathname, lineno, value); |
|
- | 1179 | } |
|
- | 1180 | else if (strncmp (token, "image=", 6) == 0) { REACH_TOKEN_VALUE (); |
|
- | 1181 | image_base = (uint32_t) read_integer (value); // read image base address |
|
- | 1182 | if ((sep = strchr (value, '-')) != NULL) image_end = (uint32_t) read_integer (sep + 1); // if we have a dash, read optional image end (TODO: check this value and produce an error in the relevant case. Not important.) |
|
- | 1183 | if ((sep = strchr (value, ',')) != NULL) image_maxsize = (uint32_t) read_integer (sep + 1); // if we have a comma, read optional image max size |
|
- | 1184 | if ((sep = strchr (value, '=')) != NULL) image_totalsize = (uint32_t) read_integer (sep + 1); // if we have an equal sign, read optional image padding size |
|
- | 1185 | if ((sep = strchr (value, '%')) != NULL) image_align = (uint32_t) read_integer (sep + 1); // if we have a modulo sign, read optional image aligmnent |
|
- | 1186 | LOG_INFO ("image 0x%x-0x%x maxsize %d totalsize %d align %d", image_base, image_end, image_maxsize, image_totalsize, image_align); |
|
- | 1187 | } |
|
- | 1188 | else if (strncmp (token, "virtual=", 8) == 0) { REACH_TOKEN_VALUE (); |
|
- | 1189 | if ((bootfile_pathname == NULL) || (startupfile_pathname == NULL) || (kernelfile_pathname == NULL)) // HACK until I figure out how to re-create them |
|
- | 1190 | DIE_WITH_EXITCODE (1, "creating bootable images require the --bootfile, --startupfile and --kernelfile command-line options in \"%s\" line %d", buildfile_pathname, lineno); |
|
- | 1191 | if ((sep = strchr (value, ',')) != NULL) // do we have a comma separating (optional) processor and boot file name ? |
|
- | 1192 | { |
|
- | 1193 | *sep = 0; |
|
- | 1194 | strcpy_s (image_processor, sizeof (image_processor), value); // save processor |
|
- | 1195 | value = sep + 1; |
|
- | 1196 | } |
|
- | 1197 | //sprintf (image_bootfile, "%s/%s/boot/sys/%s.boot", QNX_TARGET, image_processor, value); // save preboot file name (TODO: we should search in MKIFS_PATH instead of this. Not important.) |
|
- | 1198 | //strcpy (image_bootfile, bootfile_pathname); // FIXME: HACK |
|
- | 1199 | if (stat (bootfile_pathname, &stat_buf) != 0) |
|
- | 1200 | DIE_WITH_EXITCODE (1, "unable to stat the boot file \"%s\" specified in \"%s\" line %d: %s", bootfile_pathname, buildfile_pathname, lineno, strerror (errno)); |
|
- | 1201 | bootfile_size = stat_buf.st_size; // save preboot file size |
|
- | 1202 | LOG_INFO ("processor \"%s\" bootfile \"%s\"\n", image_processor, bootfile_pathname); |
|
- | 1203 | #if 1 |
|
- | 1204 | // ###################################################################################################################################################################################################################################### |
|
- | 1205 | // # FIXME: figure out how to re-create it: linker call involved |
|
- | 1206 | // # $ x86_64-pc-nto-qnx8.0.0-ld --sysroot=${QNX_TARGET}/x86_64/ -T${QNX_TARGET}/x86_64/lib/nto.link --section-start .text=0xffff800000001000 --no-relax ${QNX_TARGET}/x86_64/boot/sys/procnto-smp-instr -o procnto-smp-instr.sym.UNSTRIPPED |
|
- | 1207 | // ###################################################################################################################################################################################################################################### |
|
- | 1208 | // if (!Buffer_ReadFromFile (&entry_parms.data, kernelfile_pathname)) |
|
- | 1209 | // DIE_WITH_EXITCODE (1, "unable to read precompiled kernel file \"%s\" specified in --kernelfile argument: %s", kernelfile_pathname, strerror (errno)); |
|
- | 1210 | #else // nonworking |
|
- | 1211 | strcpy (path_on_buildhost, "procnto-smp-instr"); |
|
- | 1212 | #endif // nonworking |
|
- | 1213 | } |
|
- | 1214 | else if (strncmp (token, "mtime=", 6) == 0) { REACH_TOKEN_VALUE (); if (strcmp (value, "*") == 0) entry_parms.mtime = UINT32_MAX; else { |
|
- | 1215 | // value *must* be "YYYY-MM-DD-HH:MM:SS" by specification |
|
- | 1216 | memset (&utc_time, 0, sizeof (utc_time)); |
|
- | 1217 | 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) |
|
- | 1218 | { |
|
- | 1219 | LOG_WARNING ("syntax error in \"%s\" line %d: mtime specification not in YYYY-MM-DD-HH:MM:SS format (skipping)", buildfile_pathname, lineno); |
|
1031 |
|
1220 | continue; // invalid attribute block, skip line |
- | 1221 | } |
|
- | 1222 | utc_time.tm_mon--; // convert month from [1-12] to [0-11] |
|
- | 1223 | entry_parms.mtime = (uint32_t) mktime (&utc_time); |
|
- | 1224 | } |
|
- | 1225 | } |
|
1032 |
|
1226 | else if (strcmp (token, "+script") == 0) { |
- | 1227 | entry_parms.is_compiled_bootscript = true; |
|
- | 1228 | ASSERT_WITH_ERRNO (Buffer_InitWithByteArray (&entry_parms.data, INITIAL_STARTUP_SCRIPT)); // FIXME: HACK until the script compiler is implemented |
|
1033 |
|
1229 | should_discard_inline_contents = true; // remember we already have data (so as to discard the inline block's contents) |
- | 1230 | } |
|
1034 |
|
1231 | else if (strcmp (token, "-script") == 0) entry_parms.is_compiled_bootscript = false; |
- | 1232 | else if (strcmp (token, "+followlink") == 0) entry_parms.should_follow_symlinks = true; |
|
- | 1233 | else if (strcmp (token, "-followlink") == 0) entry_parms.should_follow_symlinks = false; |
|
- | 1234 | else if (strcmp (token, "+autolink") == 0) entry_parms.should_autosymlink_dylib = true; |
|
- | 1235 | else if (strcmp (token, "-autolink") == 0) entry_parms.should_autosymlink_dylib = false; |
|
- | 1236 | else if (strcmp (token, "+keeplinked") == 0) entry_parms.should_keep_ld_output = true; |
|
- | 1237 | else if (strcmp (token, "-keeplinked") == 0) entry_parms.should_keep_ld_output = false; |
|
- | 1238 | else LOG_WARNING ("unimplemented attribute in \"%s\" line %d: '%s'", buildfile_pathname, lineno, token); |
|
- | 1239 | #undef REACH_TOKEN_VALUE |
|
1035 | 1240 | ||
1036 |
|
1241 | token = strtok_r (NULL, RECORD_SEP, &ctx); // proceed to next attribute token |
- | 1242 | } |
|
- | 1243 | ||
1037 |
|
1244 | line_ptr++; // reach the next character |
- | 1245 | while ((*line_ptr != 0) && isspace (*line_ptr)) |
|
1038 |
|
1246 | line_ptr++; // skip leading spaces |
- | 1247 | ||
- | 1248 | // are we at the end of the line ? if so, it means the attribute values that are set should become the default |
|
- | 1249 | if ((*line_ptr == 0) || (*line_ptr == '#')) |
|
- | 1250 | { |
|
1039 |
|
1251 | #define APPLY_DEFAULT_ATTR_NUM(attr,descr,fmt) do { if (entry_parms.attr != default_parms->attr) { \ |
- | 1252 | LOG_INFO ("changing default " descr " from " fmt " to " fmt " by attribute at \"%s\" line %d", default_parms->attr, entry_parms.attr, buildfile_pathname, lineno); \ |
|
1040 |
|
1253 | default_parms->attr = entry_parms.attr; \ |
1041 |
|
1254 | } } while (0) |
1042 |
|
1255 | #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))) { \ |
- | 1256 | 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); \ |
|
1043 |
|
1257 | default_parms->attr = entry_parms.attr; \ |
1044 |
|
1258 | } } while (0) |
- | 1259 | //APPLY_DEFAULT_ATTR_STR (new_cwd, "current working directory", "\"%s\""); |
|
- | 1260 | APPLY_DEFAULT_ATTR_STR (search, "search path list", "\"%s\""); |
|
- | 1261 | APPLY_DEFAULT_ATTR_STR (prefix, "prefix", "\"%s\""); |
|
- | 1262 | APPLY_DEFAULT_ATTR_NUM (dperms, "directory permissions", "0%o"); |
|
- | 1263 | APPLY_DEFAULT_ATTR_NUM (perms, "file permissions", "0%o"); |
|
- | 1264 | APPLY_DEFAULT_ATTR_NUM (uid, "owner ID", "%d"); |
|
- | 1265 | APPLY_DEFAULT_ATTR_NUM (gid, "group ID", "%d"); |
|
- | 1266 | APPLY_DEFAULT_ATTR_NUM (st_mode, "inode type", "0%o"); |
|
- | 1267 | APPLY_DEFAULT_ATTR_NUM (is_compiled_bootscript, "compiled script state", "%d"); |
|
- | 1268 | APPLY_DEFAULT_ATTR_NUM (should_follow_symlinks, "symlink resolution", "%d"); |
|
- | 1269 | APPLY_DEFAULT_ATTR_NUM (should_autosymlink_dylib, "dylib canonical name symlinking", "%d"); |
|
- | 1270 | APPLY_DEFAULT_ATTR_NUM (should_keep_ld_output, "linker output preservation", "%d"); |
|
1045 |
|
1271 | #undef APPLY_DEFAULT_ATTR_STR |
1046 |
|
1272 | #undef APPLY_DEFAULT_ATTR_NUM |
- | 1273 | return; // end of line reached, proceed to the next line |
|
- | 1274 | } |
|
1047 |
|
1275 | // end of attributes parsing |
- | 1276 | } // end of "this line starts with an attributes block" |
|
- | 1277 | ||
- | 1278 | // there's data in this line. We expect a filename in the IFS. Read it and unescape escaped characters |
|
- | 1279 | string_len = sprintf_s (path_in_ifs, sizeof (path_in_ifs), "%s", (entry_parms.prefix != NULL ? entry_parms.prefix : "")); |
|
- | 1280 | while ((string_len > 0) && (path_in_ifs[string_len - 1] == '/')) |
|
- | 1281 | string_len--; // chop off any trailing slashes from prefix |
|
1048 |
|
1282 | write_ptr = &path_in_ifs[string_len]; |
- | 1283 | *write_ptr++ = '/'; // add ONE trailing slash |
|
- | 1284 | specifiedpathname_start = write_ptr; // remember the specified pathname will start here |
|
- | 1285 | is_quoted_context = (*line_ptr == '"'); |
|
- | 1286 | if (is_quoted_context) |
|
- | 1287 | line_ptr++; // skip a possible initial quote |
|
- | 1288 | if (*line_ptr == '/') |
|
- | 1289 | { |
|
- | 1290 | LOG_WARNING ("paths in the IFS file should not begin with a leading '/' in \"%s\" line %d", buildfile_pathname, lineno); |
|
- | 1291 | line_ptr++; // consistency check: paths in the IFS should not begin with a '/' |
|
1049 | } |
1292 | } |
- | 1293 | while ((*line_ptr != 0) && ((!is_quoted_context && (*line_ptr != '=') && !isspace (*line_ptr)) || (is_quoted_context && (*line_ptr == '"')))) |
|
- | 1294 | { |
|
- | 1295 | if (*line_ptr == '\\') |
|
- | 1296 | { |
|
- | 1297 | line_ptr++; |
|
- | 1298 | *write_ptr++ = *line_ptr; // unescape characters that are escaped with '\' |
|
- | 1299 | } |
|
- | 1300 | else |
|
- | 1301 | *write_ptr++ = *line_ptr; |
|
- | 1302 | line_ptr++; |
|
- | 1303 | } |
|
- | 1304 | *write_ptr = 0; // terminate the string |
|
- | 1305 | if (is_quoted_context && (*line_ptr == '"')) |
|
- | 1306 | line_ptr++; // skip a possible final quote |
|
- | 1307 | ||
- | 1308 | // we reached a space OR an equal sign |
|
- | 1309 | while ((*line_ptr != 0) && isspace (*line_ptr)) |
|
- | 1310 | line_ptr++; // skip optional spaces after the filename in the IFS |
|
- | 1311 | ||
- | 1312 | // do we have an equal sign ? |
|
- | 1313 | if (*line_ptr == '=') // we must be creating either a directory or a file, do we have an equal sign ? |
|
- | 1314 | { |
|
- | 1315 | line_ptr++; // skip the equal sign |
|
- | 1316 | while ((*line_ptr != 0) && isspace (*line_ptr)) |
|
- | 1317 | line_ptr++; // skip optional spaces after the equal sign |
|
- | 1318 | ||
- | 1319 | if (*line_ptr == 0) |
|
- | 1320 | { |
|
- | 1321 | LOG_WARNING ("syntax error in \"%s\" line %d: missing data specification after equal sign (skipping)", buildfile_pathname, lineno); |
|
- | 1322 | return; // invalid symlink specification, skip line |
|
- | 1323 | } |
|
- | 1324 | ||
- | 1325 | // read the host system's path, it may be either a path or a contents definition. Is it a content definition ? |
|
- | 1326 | if (*line_ptr == '{') |
|
- | 1327 | { |
|
- | 1328 | allocated_size = 0; |
|
- | 1329 | ||
- | 1330 | line_ptr++; // skip the leading content definition |
|
- | 1331 | is_escaped_char = false; |
|
- | 1332 | for (;;) |
|
- | 1333 | { |
|
- | 1334 | read_char = fgetc (buildfile_fp); |
|
- | 1335 | if (read_char == EOF) |
|
- | 1336 | DIE_WITH_EXITCODE (1, "syntax error in \"%s\" line %d: unterminated contents block (end of file reached)", buildfile_pathname, lineno); // invalid contents block |
|
- | 1337 | else if ((read_char == '\\') && !is_escaped_char) |
|
- | 1338 | is_escaped_char = true; // remember the next char is escaped |
|
- | 1339 | else if ((read_char == '}') && !is_escaped_char) |
|
- | 1340 | break; // found an unescaped closing bracked, stop parsing |
|
- | 1341 | else |
|
- | 1342 | { |
|
- | 1343 | is_escaped_char = false; // any other char, meaning the next one will not be escaped |
|
- | 1344 | if (!should_discard_inline_contents) // only store the contents if we do NOT know the data yet |
|
- | 1345 | { |
|
- | 1346 | if (entry_parms.data.size == allocated_size) // reallocate in 4 kb blocks |
|
- | 1347 | { |
|
- | 1348 | reallocated_ptr = realloc (entry_parms.data.bytes, allocated_size + 4096); |
|
- | 1349 | ASSERT_WITH_ERRNO (reallocated_ptr); |
|
- | 1350 | entry_parms.data.bytes = reallocated_ptr; |
|
- | 1351 | allocated_size += 4096; |
|
- | 1352 | } |
|
- | 1353 | entry_parms.data.bytes[entry_parms.data.size++] = read_char; |
|
- | 1354 | } |
|
- | 1355 | if (read_char == '\n') |
|
- | 1356 | lineno++; // update line counter as we parse the inline content |
|
- | 1357 | } |
|
- | 1358 | } // end for |
|
- | 1359 | } |
|
- | 1360 | else // not a content definition between { brackets }, must be either a pathname on the build host, or the target of a symlink |
|
- | 1361 | { |
|
- | 1362 | is_quoted_context = (*line_ptr == '"'); |
|
- | 1363 | if (is_quoted_context) |
|
- | 1364 | line_ptr++; // skip a possible initial quote |
|
- | 1365 | specifiedpathname_start = line_ptr; // remember where the specified pathname starts |
|
- | 1366 | write_ptr = line_ptr; // now unescape all characters |
|
- | 1367 | while ((*line_ptr != 0) && ((!is_quoted_context && !isspace (*line_ptr)) || (is_quoted_context && (*line_ptr == '"')))) |
|
- | 1368 | { |
|
- | 1369 | if (*line_ptr == '\\') |
|
- | 1370 | { |
|
- | 1371 | line_ptr++; |
|
- | 1372 | *write_ptr++ = *line_ptr; // unescape characters that are escaped with '\' |
|
- | 1373 | } |
|
- | 1374 | else |
|
- | 1375 | *write_ptr++ = *line_ptr; |
|
- | 1376 | line_ptr++; |
|
- | 1377 | } |
|
- | 1378 | *write_ptr = 0; // terminate the string |
|
- | 1379 | if (is_quoted_context && (*line_ptr == '"')) |
|
- | 1380 | line_ptr++; // skip a possible final quote |
|
- | 1381 | ||
- | 1382 | if (S_ISLNK (entry_parms.st_mode)) // are we storing a symlink ? |
|
- | 1383 | ASSERT_WITH_ERRNO (Buffer_InitWithCString (&entry_parms.data, specifiedpathname_start)); // if so, store the symlink target as the dirent's blob data |
|
- | 1384 | else // it's a build host filesystem path |
|
- | 1385 | strcpy_s (path_on_buildhost, sizeof (path_on_buildhost), specifiedpathname_start); // the path on the build host is given after the equal sign |
|
- | 1386 | } |
|
- | 1387 | } |
|
- | 1388 | else // no equal sign, meaning the file will have the same name on the build host filesystem |
|
- | 1389 | { |
|
- | 1390 | // consistency check: symlinks MUST have an equal sign |
|
- | 1391 | if (entry_parms.st_mode == S_IFLNK) |
|
- | 1392 | { |
|
- | 1393 | LOG_WARNING ("syntax error in \"%s\" line %d: missing equal sign and symlink target (skipping)", buildfile_pathname, lineno); |
|
- | 1394 | return; // invalid symlink specification, skip line |
|
- | 1395 | } |
|
- | 1396 | ||
- | 1397 | strcpy_s (path_on_buildhost, sizeof (path_on_buildhost), specifiedpathname_start); // the path on the build host is the one specified |
|
- | 1398 | sep = strrchr (specifiedpathname_start, '/'); |
|
- | 1399 | if (sep != NULL) |
|
- | 1400 | memmove (specifiedpathname_start, sep + 1, strlen (sep + 1) + 1); // the path in the IFS will be the BASENAME of the path specified (after the prefix) |
|
- | 1401 | } |
|
- | 1402 | ||
- | 1403 | // now add this entry to the image filesystem |
|
- | 1404 | if (S_ISDIR (entry_parms.st_mode)) |
|
- | 1405 | entry_parms.st_mode |= entry_parms.dperms; |
|
- | 1406 | else if (S_ISLNK (entry_parms.st_mode)) |
|
- | 1407 | entry_parms.st_mode |= 0777; // NOTE: mkifs sets symlink permissions to rwxrwxrwx !? |
|
- | 1408 | else // file or device node |
|
- | 1409 | entry_parms.st_mode |= entry_parms.perms; |
|
- | 1410 | ||
- | 1411 | add_fsentry (fsentries, fsentry_count, &entry_parms, path_in_ifs, path_on_buildhost); // and add filesystem entry |
|
- | 1412 | ||
- | 1413 | if (entry_parms.data.bytes != NULL) |
|
- | 1414 | free (entry_parms.data.bytes); // if blob data was allocated, free it |
|
1050 | 1415 | ||
1051 | return; |
1416 | return; // finished parsing that line |
1052 | } |
1417 | } |
1053 | 1418 | ||
1054 | 1419 | ||
1055 | int main (int argc, char **argv) |
1420 | int main (int argc, char **argv) |
1056 | { |
1421 | { |
Line 1088... | Line 1453... | ||
1088 | .prefix = "/proc/boot", |
1453 | .prefix = "/proc/boot", |
1089 | .should_follow_symlinks = true, // [+|-followlink] |
1454 | .should_follow_symlinks = true, // [+|-followlink] |
1090 | .should_autosymlink_dylib = true, // [+|-autolink] |
1455 | .should_autosymlink_dylib = true, // [+|-autolink] |
1091 | .is_compiled_bootscript = false, // [+|-script] |
1456 | .is_compiled_bootscript = false, // [+|-script] |
1092 | .extra_ino_flags = 0, |
1457 | .extra_ino_flags = 0, |
1093 | .search = |
1458 | .search = NULL, |
1094 | .data = { NULL, 0 } |
1459 | .data = { NULL, 0 } |
1095 | }; |
1460 | }; |
1096 | static parms_t entry_parms = { 0 }; // current parameters for a filesystem entry (will be initialized to default_parms each time a new entry is parsed in the build file) |
1461 | static parms_t entry_parms = { 0 }; // current parameters for a filesystem entry (will be initialized to default_parms each time a new entry is parsed in the build file) |
1097 | 1462 | ||
1098 | char path_on_buildhost[MAXPATHLEN] = ""; |
1463 | char path_on_buildhost[MAXPATHLEN] = ""; |
1099 | char path_in_ifs[MAXPATHLEN] = ""; |
1464 | char path_in_ifs[MAXPATHLEN] = ""; |
1100 | char *ifs_pathname = NULL; |
1465 | char *ifs_pathname = NULL; |
1101 | void *reallocated_ptr; |
1466 | void *reallocated_ptr; |
1102 | struct tm utc_time; |
- | |
1103 | struct stat stat_buf; |
- | |
1104 | size_t |
1467 | size_t reallocated_size; |
1105 | size_t available_space; |
1468 | size_t available_space; |
1106 | size_t allocated_size; |
- | |
1107 | size_t fsentry_index; |
1469 | size_t fsentry_index; |
1108 | size_t largest_index; |
1470 | size_t largest_index; |
1109 | size_t largest_size; |
1471 | size_t largest_size; |
- | 1472 | size_t imgdir_size; |
|
1110 | size_t curr_offset; |
1473 | size_t curr_offset; |
1111 | ifs_t ifs = { 0 }; |
1474 | ifs_t ifs = { 0 }; |
1112 | int32_t checksum; |
1475 | int32_t checksum; |
1113 | char *first_pathname = NULL; |
1476 | char *first_pathname = NULL; |
1114 | char *second_pathname = NULL; |
1477 | char *second_pathname = NULL; |
1115 | char *specifiedpathname_start; |
- | |
1116 | char *directiveblock_start; |
- | |
1117 | char *write_ptr; |
- | |
1118 | char *line_ptr; |
- | |
1119 | char *token; |
- | |
1120 | char *value; |
- | |
1121 | char *sep; |
1478 | char *sep; |
1122 | char *ctx; |
- | |
1123 | int arg_index; |
1479 | int arg_index; |
1124 | bool is_quoted_context = false; |
1480 | bool is_quoted_context = false; |
1125 | bool is_escaped_char = false; |
1481 | bool is_escaped_char = false; |
1126 | bool should_discard_inline_contents = false; |
1482 | bool should_discard_inline_contents = false; |
1127 | bool want_info = false; |
1483 | bool want_info = false; |
Line 1129... | Line 1485... | ||
1129 | bool want_help = false; |
1485 | bool want_help = false; |
1130 | bool want_dump = false; |
1486 | bool want_dump = false; |
1131 | bool want_strip = false; |
1487 | bool want_strip = false; |
1132 | bool want_hexdump = false; |
1488 | bool want_hexdump = false; |
1133 | bool is_foreign_endianness; |
1489 | bool is_foreign_endianness; |
1134 | int string_len; |
- | |
1135 | int read_char; |
- | |
1136 | FILE *buildfile_fp; |
1490 | FILE *buildfile_fp; |
- | 1491 | ||
- | 1492 | // initialize stuff |
|
- | 1493 | saved_ELF_sections = (char **) malloc (4 * sizeof (char *)); |
|
- | 1494 | ASSERT_WITH_ERRNO (saved_ELF_sections); |
|
- | 1495 | 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 |
|
- | 1496 | saved_ELF_sections[1] = ".gnu_debuglink"; |
|
- | 1497 | saved_ELF_sections[2] = "QNX_usage"; |
|
- | 1498 | saved_ELF_sections[3] = ".note.gnu.build-id"; // undocumented by QNX, but nonetheless preserved |
|
- | 1499 | saved_ELF_section_count = 4; |
|
1137 | 1500 | ||
1138 | // parse arguments |
1501 | // parse arguments |
1139 | for (arg_index = 1; arg_index < argc; arg_index++) |
1502 | for (arg_index = 1; arg_index < argc; arg_index++) |
1140 | { |
1503 | { |
1141 | if ((strcmp (argv[arg_index], "--bootfile") == 0) && (arg_index + 1 < argc)) // --bootfile path/to/blob.bin |
1504 | if ((strcmp (argv[arg_index], "--bootfile") == 0) && (arg_index + 1 < argc)) // --bootfile path/to/blob.bin |
Line 1179... | Line 1542... | ||
1179 | want_strip = true; |
1542 | want_strip = true; |
1180 | else if (strcmp (argv[arg_index], "--everything") == 0) |
1543 | else if (strcmp (argv[arg_index], "--everything") == 0) |
1181 | want_everything = true; |
1544 | want_everything = true; |
1182 | else if (strncmp (argv[arg_index], "-v", 2) == 0) // -v[....] |
1545 | else if (strncmp (argv[arg_index], "-v", 2) == 0) // -v[....] |
1183 | verbose_level += (int) strlen (argv[arg_index] + 1); // increase verbosity by the number of characters in this flag |
1546 | verbose_level += (int) strlen (argv[arg_index] + 1); // increase verbosity by the number of characters in this flag |
- | 1547 | else if ((strcmp (argv[arg_index], "-l") == 0) && (arg_index + 1 < argc)) |
|
- | 1548 | arg_index++; // these args will be parsed once the build file is open |
|
- | 1549 | else if ((strcmp (argv[arg_index], "-r") == 0) && (arg_index + 1 < argc)) |
|
- | 1550 | { |
|
- | 1551 | reallocated_size = (SEARCH_PATH != NULL ? strlen (SEARCH_PATH) + 1 : 0) + strlen (argv[arg_index + 1]) + 1; |
|
- | 1552 | reallocated_ptr = realloc (SEARCH_PATH, reallocated_size); // grow search prefixes array |
|
- | 1553 | ASSERT_WITH_ERRNO (reallocated_ptr); |
|
- | 1554 | if (SEARCH_PATH != NULL) |
|
- | 1555 | strcat_s (reallocated_ptr, reallocated_size, PATH_SEP); |
|
- | 1556 | strcat_s (reallocated_ptr, reallocated_size, argv[++arg_index]); // stack up another search prefix |
|
- | 1557 | SEARCH_PATH = reallocated_ptr; |
|
- | 1558 | } |
|
- | 1559 | else if ((strcmp (argv[arg_index], "-s") == 0) && (arg_index + 1 < argc)) |
|
- | 1560 | { |
|
- | 1561 | reallocated_ptr = realloc (saved_ELF_sections, (saved_ELF_section_count + 1) * sizeof (char *)); // grow ELF sections array |
|
- | 1562 | ASSERT_WITH_ERRNO (reallocated_ptr); |
|
- | 1563 | saved_ELF_sections = reallocated_ptr; |
|
- | 1564 | saved_ELF_sections[saved_ELF_section_count++] = argv[++arg_index]; // stack up another ELF section name to preserve |
|
- | 1565 | } |
|
1184 | else if ((strcmp (argv[arg_index], "-?") == 0) || (strcmp (argv[arg_index], "--help") == 0)) |
1566 | else if ((strcmp (argv[arg_index], "-?") == 0) || (strcmp (argv[arg_index], "--help") == 0)) |
1185 | want_help = true; |
1567 | want_help = true; |
1186 | else if (first_pathname == NULL) |
1568 | else if (first_pathname == NULL) |
1187 | first_pathname = argv[arg_index]; |
1569 | first_pathname = argv[arg_index]; |
1188 | else if (second_pathname == NULL) |
1570 | else if (second_pathname == NULL) |
Line 1198... | Line 1580... | ||
1198 | fprintf (out, "ifstool - QNX in-kernel filesystem creation utility by Pierre-Marie Baty <pm@pmbaty.com>\n"); |
1580 | fprintf (out, "ifstool - QNX in-kernel filesystem creation utility by Pierre-Marie Baty <pm@pmbaty.com>\n"); |
1199 | fprintf (out, " version " VERSION_FMT_YYYYMMDD "\n", VERSION_ARG_YYYYMMDD); |
1581 | fprintf (out, " version " VERSION_FMT_YYYYMMDD "\n", VERSION_ARG_YYYYMMDD); |
1200 | if (!want_help) |
1582 | if (!want_help) |
1201 | fprintf (out, "error: missing parameters\n"); |
1583 | fprintf (out, "error: missing parameters\n"); |
1202 | fprintf (out, "usage:\n"); |
1584 | fprintf (out, "usage:\n"); |
1203 | fprintf (out, " ifstool [--bootfile <pathname>] [--startupfile <pathname>@<EP_from_imgbase>] [--kernelfile <pathname>@<fileoffs>] [-n[n]] [-v[...]] <buildfile> <outfile>\n"); |
- | |
1204 | fprintf (out, " ifstool --info [--everything] <ifs file>\n"); |
1585 | fprintf (out, " ifstool --info [--everything] <ifs file>\n"); |
1205 | fprintf (out, " ifstool --dump [--outdir <path>] <ifs file>\n"); |
1586 | fprintf (out, " ifstool --dump [--outdir <path>] <ifs file>\n"); |
1206 | fprintf (out, " ifstool --strip [--outfile <pathname>] <ELF file>\n"); |
1587 | fprintf (out, " ifstool --strip [--outfile <pathname>] <ELF file>\n"); |
1207 | fprintf (out, " ifstool --help\n"); |
1588 | fprintf (out, " ifstool [-?|--help]\n"); |
- | 1589 | fprintf (out, " ifstool [--bootfile <pathname>] [--startupfile <pathname>@<EP_from_imgbase>] [--kernelfile <pathname>@<fileoffs>] [-l inputline] [-n[n]] [-r rootdir] [-v[...]] <buildfile> <outfile>\n"); |
|
1208 | fprintf (out, "NOTE: the |
1590 | fprintf (out, "NOTE: the compiler mode requires predigested boot, startup and kernel files produced by mkifs.\n"); |
- | 1591 | fprintf (out, "options:\n"); |
|
- | 1592 | fprintf (out, " -? Display some help information.\n"); |
|
- | 1593 | // fprintf (out, " -a .ext Append a suffix to symbol files generated via [+keeplinked].\n"); |
|
- | 1594 | fprintf (out, " -l line Process line before interpreting the buildfile. Input lines given\n"); |
|
- | 1595 | fprintf (out, " to mkifs should be quoted to prevent interpretation by the shell\n"); |
|
- | 1596 | fprintf (out, " (especially as mkifs input lines often contain spaces). Multiple\n"); |
|
- | 1597 | fprintf (out, " -l options are processed in the order specified. No default.\n"); |
|
- | 1598 | fprintf (out, " -n[n] Force the modification times of all inline files to be 0. If you\n"); |
|
- | 1599 | fprintf (out, " specify -nn, mkifs sets the modification times of all files to 0.\n"); |
|
- | 1600 | fprintf (out, " When mkifs adds files to an IFS image, it uses the timestamp info\n"); |
|
- | 1601 | fprintf (out, " from the file on the host machine. If mkifs is creating an inline\n"); |
|
- | 1602 | fprintf (out, " file (which doesn't exist on the host machine), it must generate\n"); |
|
- | 1603 | fprintf (out, " its own timestamp information. By default, it's the time at which\n"); |
|
- | 1604 | fprintf (out, " the image is generated. This results in different checksum values\n"); |
|
- | 1605 | fprintf (out, " for two identical builds, because the file's times are different.\n"); |
|
- | 1606 | fprintf (out, " If you use -n, the checksum value is the same on all identical\n"); |
|
- | 1607 | fprintf (out, " builds. The -nn option addresses a quirk in NTFS with daylight\n"); |
|
- | 1608 | fprintf (out, " savings time. This forces the modification time for all files in\n"); |
|
- | 1609 | fprintf (out, " the IFS image to be set to 0. This ensures that subsequent builds\n"); |
|
- | 1610 | fprintf (out, " of the same IFS image have the same checksum."); |
|
- | 1611 | // fprintf (out, " -o dir Specify a directory to be used for all permanent build artifacts,\n"); |
|
- | 1612 | // fprintf (out, " other than the output image itself. The most common example is\n"); |
|
- | 1613 | // fprintf (out, " the .sym files generated by the [+keeplinked] attribute.\n"); |
|
- | 1614 | // fprintf (out, " -p file Apply patching instructions from this file.\n"); |
|
- | 1615 | fprintf (out, " -r dir When searching for host files to be included in the image, search\n"); |
|
- | 1616 | fprintf (out, " the default paths used for storing binaries within the specified\n"); |
|
- | 1617 | fprintf (out, " directory before searching the default paths within $QNX_TARGET.\n"); |
|
- | 1618 | fprintf (out, " You can define multiple -r options; each adds a set of paths to\n"); |
|
- | 1619 | fprintf (out, " search for files. The -r options are evaluated from left to right\n"); |
|
- | 1620 | fprintf (out, " meaning the paths prefixed with the first (leftmost) rootdir are\n"); |
|
- | 1621 | fprintf (out, " searched first, then those prefixed with the second rootdir, and\n"); |
|
- | 1622 | fprintf (out, " so on.\n"); |
|
- | 1623 | fprintf (out, " Normally, mkifs searches any paths defined in $MKIFS_PATH when\n"); |
|
- | 1624 | fprintf (out, " it was called and then the default paths within $QNX_TARGET. The\n"); |
|
- | 1625 | fprintf (out, " default paths are based on the CPU architecture specified by\n"); |
|
- | 1626 | fprintf (out, " $PROCESSOR and $PROCESSOR_BASE. If you specify -r options, mkifs\n"); |
|
- | 1627 | fprintf (out, " searches the default paths prefixed with each dir variable before\n"); |
|
- | 1628 | fprintf (out, " searching those within $QNX_TARGET. These paths are:\n"); |
|
- | 1629 | fprintf (out, " dir/${PROCESSOR}/sbin\n"); |
|
- | 1630 | fprintf (out, " dir/${PROCESSOR}/usr/sbin\n"); |
|
- | 1631 | fprintf (out, " dir/${PROCESSOR}/boot/sys\n"); |
|
- | 1632 | fprintf (out, " dir/${PROCESSOR_BASE}/boot/sys\n"); |
|
- | 1633 | fprintf (out, " dir/${PROCESSOR}/bin\n"); |
|
- | 1634 | fprintf (out, " dir/${PROCESSOR}/usr/bin\n"); |
|
- | 1635 | fprintf (out, " dir/${PROCESSOR}/lib\n"); |
|
- | 1636 | fprintf (out, " dir/${PROCESSOR}/lib/dll\n"); |
|
- | 1637 | fprintf (out, " dir/${PROCESSOR}/usr/lib\n"); |
|
- | 1638 | fprintf (out, " NOTE: The structure of the directory paths under dir must be\n"); |
|
- | 1639 | fprintf (out, " identical to that of the default paths under $QNX_TARGET, but the\n"); |
|
- | 1640 | fprintf (out, " root dir itself may be any path you choose. For example, if you\n"); |
|
- | 1641 | fprintf (out, " wanted to include /scratch/aarch64le/sbin/devb-sata, you would\n"); |
|
- | 1642 | fprintf (out, " specify a -r option like this:\n"); |
|
- | 1643 | fprintf (out, " -r /scratch\n"); |
|
- | 1644 | fprintf (out, " Note that you don't include $PROCESSOR or $PROCESSOR_BASE in dir.\n"); |
|
- | 1645 | fprintf (out, " -s name Don't strip the named section from ELF executables when creating\n"); |
|
- | 1646 | fprintf (out, " an IFS image. You can use this option more than once to specify\n"); |
|
- | 1647 | fprintf (out, " additional sections. By default, mkifs doesn't strip:\n"); |
|
- | 1648 | fprintf (out, " .gnu_debuglink - the name and checksum of the debug info file\n"); |
|
- | 1649 | fprintf (out, " QNX_info - build properties\n"); |
|
- | 1650 | fprintf (out, " QNX_usage - usage message\n"); |
|
- | 1651 | fprintf (out, " You can use the keepsection attribute to specify the sections\n"); |
|
- | 1652 | fprintf (out, " that are not to be stripped from specific files in the image. For\n"); |
|
- | 1653 | fprintf (out, " files in the bootstrap section (like startup or procnto), the\n"); |
|
- | 1654 | fprintf (out, " global keepsection list affected by -s does not apply to these\n"); |
|
- | 1655 | fprintf (out, " files. For them, only the QNX_info section is kept.\n"); |
|
- | 1656 | fprintf (out, " -v[v..] Operate verbosely. Specifying additional v options increases the\n"); |
|
- | 1657 | fprintf (out, " verbosity.\n"); |
|
1209 | exit (want_help ? 0 : 1); |
1658 | exit (want_help ? 0 : 1); |
1210 | } |
1659 | } |
1211 | 1660 | ||
1212 | // do we want info about a particular IFS ? if so, dissecate it |
1661 | // do we want info about a particular IFS ? if so, dissecate it |
1213 | if (want_info) |
1662 | if (want_info) |
Line 1224... | Line 1673... | ||
1224 | // else do we want to strip an ELF file ? if so, do so |
1673 | // else do we want to strip an ELF file ? if so, do so |
1225 | else if (want_strip) |
1674 | else if (want_strip) |
1226 | { |
1675 | { |
1227 | buffer_t file; |
1676 | buffer_t file; |
1228 | ASSERT (Buffer_ReadFromFile (&file, first_pathname), "can't open \"%s\" for reading: %s", first_pathname, strerror (errno)); |
1677 | ASSERT (Buffer_ReadFromFile (&file, first_pathname), "can't open \"%s\" for reading: %s", first_pathname, strerror (errno)); |
1229 | ASSERT (Buffer_StripELFFile (&file, first_pathname), "error stripping \"%s\": %s", first_pathname, strerror (errno)); |
1678 | ASSERT (Buffer_StripELFFile (&file, saved_ELF_sections, saved_ELF_section_count, first_pathname), "error stripping \"%s\": %s", first_pathname, strerror (errno)); |
1230 | ASSERT_WITH_ERRNO (Buffer_WriteToFile (&file, (second_pathname != NULL ? second_pathname : "<stdout>"))); |
1679 | ASSERT_WITH_ERRNO (Buffer_WriteToFile (&file, (second_pathname != NULL ? second_pathname : "<stdout>"))); |
1231 | exit (0); |
1680 | exit (0); |
1232 | } |
1681 | } |
1233 | 1682 | ||
1234 | // we want to CREATE an IFS file |
1683 | // we want to CREATE an IFS file |
Line 1239... | Line 1688... | ||
1239 | QNX_TARGET = getenv ("QNX_TARGET"); |
1688 | QNX_TARGET = getenv ("QNX_TARGET"); |
1240 | if (QNX_TARGET == NULL) |
1689 | if (QNX_TARGET == NULL) |
1241 | DIE_WITH_EXITCODE (1, "the QNX_TARGET environment variable is not set"); |
1690 | DIE_WITH_EXITCODE (1, "the QNX_TARGET environment variable is not set"); |
1242 | else if (access (QNX_TARGET, 0) != 0) |
1691 | else if (access (QNX_TARGET, 0) != 0) |
1243 | DIE_WITH_EXITCODE (1, "the QNX_TARGET environment variable doesn't point to an existing directory"); |
1692 | DIE_WITH_EXITCODE (1, "the QNX_TARGET environment variable doesn't point to an existing directory"); |
1244 | - | ||
1245 | // prepare a default MKIFS_PATH assuming the host processor |
- | |
1246 | update_MKIFS_PATH (image_processor); |
- | |
1247 | 1693 | ||
1248 | // open build file |
1694 | // open build file |
1249 | fopen_s (&buildfile_fp, buildfile_pathname, "rb"); |
1695 | fopen_s (&buildfile_fp, buildfile_pathname, "rb"); |
1250 | if (buildfile_fp == NULL) |
1696 | if (buildfile_fp == NULL) |
1251 | DIE_WITH_EXITCODE (1, "unable to open build file \"%s\" for reading: %s", buildfile_pathname, strerror (errno)); |
1697 | DIE_WITH_EXITCODE (1, "unable to open build file \"%s\" for reading: %s", buildfile_pathname, strerror (errno)); |
1252 | 1698 | ||
1253 | // stack up filesystem entries |
1699 | // stack up filesystem entries |
1254 | memcpy (&entry_parms, &default_parms, sizeof (default_parms)); |
1700 | memcpy (&entry_parms, &default_parms, sizeof (default_parms)); |
1255 | entry_parms.st_mode = S_IFDIR | default_parms.dperms; |
1701 | entry_parms.st_mode = S_IFDIR | default_parms.dperms; |
1256 | add_fsentry (&fsentries, &fsentry_count, &entry_parms, "", NULL); // add the root dir first |
1702 | add_fsentry (&fsentries, &fsentry_count, &entry_parms, "", NULL); // add the root dir first |
- | 1703 | ||
- | 1704 | // parse -l arguments before everything else |
|
- | 1705 | for (arg_index = 1; arg_index < argc; arg_index++) |
|
- | 1706 | if ((strcmp (argv[arg_index], "-l") == 0) && (arg_index + 1 < argc)) |
|
- | 1707 | parse_line (NULL, argv[++arg_index], &fsentries, &fsentry_count, &default_parms); |
|
1257 | 1708 | ||
1258 | // parse the IFS build file line per line |
1709 | // parse the IFS build file line per line |
1259 | while (fgets (line_buffer, sizeof (line_buffer), buildfile_fp) != NULL) |
1710 | while (fgets (line_buffer, sizeof (line_buffer), buildfile_fp) != NULL) |
1260 | { |
1711 | { |
1261 | if (current_line != NULL) |
1712 | if (current_line != NULL) |
1262 | free (current_line); |
1713 | free (current_line); |
1263 | current_line = strdup (line_buffer); |
1714 | current_line = strdup (line_buffer); |
1264 | ASSERT_WITH_ERRNO (current_line); |
1715 | ASSERT_WITH_ERRNO (current_line); |
1265 | lineno++; // keep track of current line number |
1716 | lineno++; // keep track of current line number |
- | 1717 | parse_line (buildfile_fp, line_buffer, &fsentries, &fsentry_count, &default_parms); |
|
- | 1718 | } |
|
1266 | 1719 | ||
1267 | line_ptr = line_buffer; |
- | |
1268 |
|
1720 | fclose (buildfile_fp); // finished parsing the build file |
1269 | line_ptr++; // skip leading spaces |
- | |
1270 | 1721 | ||
1271 | if ((*line_ptr == 0) || (*line_ptr == '#')) |
- | |
1272 | continue; // skip empty or comment lines |
- | |
1273 | - | ||
1274 |
|
1722 | // parse the IFS build file line per line |
1275 |
|
1723 | while (fgets (line_buffer, sizeof (line_buffer), buildfile_fp) != NULL) |
1276 | line_buffer[string_len - 1] = 0; // chop off newline for easier debug output |
- | |
1277 | - | ||
1278 | // reset entry values |
- | |
1279 | memcpy (&entry_parms, &default_parms, sizeof (default_parms)); |
- | |
1280 | path_in_ifs[0] = 0; |
- | |
1281 | path_on_buildhost[0] = 0; |
- | |
1282 | should_discard_inline_contents = false; |
- | |
1283 | - | ||
1284 | // does this line start with an attribute block ? |
- | |
1285 | if (*line_ptr == '[') |
- | |
1286 |
|
1724 | { |
1287 | line_ptr++; // skip the leading square bracket |
- | |
1288 | directiveblock_start = line_ptr; // remember where it starts |
- | |
1289 | is_quoted_context = false; |
- | |
1290 | while ((*line_ptr != 0) && !((*line_ptr == ']') && (line_ptr[-1] != '\\') && !is_quoted_context)) |
- | |
1291 | { |
- | |
1292 | if (*line_ptr == '"') |
- | |
1293 | is_quoted_context ^= true; // remember when we're between quotes |
- | |
1294 | else if (!is_quoted_context && (*line_ptr == ' ')) |
- | |
1295 | *line_ptr = RECORD_SEP[0]; // turn all spaces outside quoted contexts into an ASCII record separator to ease token splitting |
- | |
1296 | line_ptr++; // reach the next unescaped closing square bracket |
- | |
1297 | } |
- | |
1298 | if (*line_ptr != ']') |
- | |
1299 | { |
- | |
1300 | LOG ("warning", 0, "syntax error in \"%s\" line %d: unterminated attributes block (skipping)", buildfile_pathname, lineno); |
- | |
1301 | continue; // invalid attribute block, skip line |
- | |
1302 | } |
- | |
1303 | *line_ptr = 0; // end the attribute block so that it is a parsable C string |
- | |
1304 | - | ||
1305 | // now parse the attribute tokens |
- | |
1306 | // DOCUMENTATION: https://www.qnx.com/developers/docs/8.0/com.qnx.doc.neutrino.utilities/topic/m/mkifs.html#mkifs__description |
- | |
1307 | token = strtok_r (directiveblock_start, RECORD_SEP, &ctx); |
- | |
1308 |
|
1725 | if (current_line != NULL) |
1309 | { |
- | |
1310 | // evaluate attribute token |
- | |
1311 | #define REACH_TOKEN_VALUE() do { value = strchr (token, '=') + 1; if (*value == '"') value++; } while (0) |
- | |
1312 | if (strncmp (token, "uid=", 4) == 0) { REACH_TOKEN_VALUE (); entry_parms.uid = (int) read_integer (value); } |
- | |
1313 | else if (strncmp (token, "gid=", 4) == 0) { REACH_TOKEN_VALUE (); entry_parms.gid = (int) read_integer (value); } |
- | |
1314 | else if (strncmp (token, "dperms=", 7) == 0) { REACH_TOKEN_VALUE (); entry_parms.dperms = (int) read_integer (value); } |
- | |
1315 | else if (strncmp (token, "perms=", 6) == 0) { REACH_TOKEN_VALUE (); entry_parms.perms = (int) read_integer (value); } |
- | |
1316 | else if (strncmp (token, "type=", 5) == 0) { REACH_TOKEN_VALUE (); |
- | |
1317 | if (strcmp (value, "dir") == 0) entry_parms.st_mode = S_IFDIR; |
- | |
1318 | else if (strcmp (value, "file") == 0) entry_parms.st_mode = S_IFREG; |
- | |
1319 | else if (strcmp (value, "link") == 0) entry_parms.st_mode = S_IFLNK; |
- | |
1320 | else if (strcmp (value, "fifo") == 0) entry_parms.st_mode = S_IFIFO; |
- | |
1321 | else DIE_WITH_EXITCODE (1, "invalid 'type' attribute in \"%s\" line %d: '%s'", buildfile_pathname, lineno, value); |
- | |
1322 | } |
- | |
1323 | else if (strncmp (token, "prefix=", 7) == 0) { REACH_TOKEN_VALUE (); strcpy_s (entry_parms.prefix, sizeof (entry_parms.prefix), (*value == '/' ? value + 1 : value)); } // skip possible leading slash in prefix |
- | |
1324 | else if (strncmp (token, "image=", 6) == 0) { REACH_TOKEN_VALUE (); |
- | |
1325 | image_base = (uint32_t) read_integer (value); // read image base address |
- | |
1326 | if ((sep = strchr (value, '-')) != NULL) image_end = (uint32_t) read_integer (sep + 1); // if we have a dash, read optional image end (TODO: check this value and produce an error in the relevant case. Not important.) |
- | |
1327 | if ((sep = strchr (value, ',')) != NULL) image_maxsize = (uint32_t) read_integer (sep + 1); // if we have a comma, read optional image max size |
- | |
1328 | if ((sep = strchr (value, '=')) != NULL) image_totalsize = (uint32_t) read_integer (sep + 1); // if we have an equal sign, read optional image padding size |
- | |
1329 | if ((sep = strchr (value, '%')) != NULL) image_align = (uint32_t) read_integer (sep + 1); // if we have a modulo sign, read optional image aligmnent |
- | |
1330 | LOG_INFO ("image 0x%x-0x%x maxsize %d totalsize %d align %d", image_base, image_end, image_maxsize, image_totalsize, image_align); |
- | |
1331 | } |
- | |
1332 | else if (strncmp (token, "virtual=", 8) == 0) { REACH_TOKEN_VALUE (); |
- | |
1333 | if ((bootfile_pathname == NULL) || (startupfile_pathname == NULL) || (kernelfile_pathname == NULL)) // HACK until I figure out how to re-create them |
- | |
1334 | DIE_WITH_EXITCODE (1, "creating bootable images require the --bootfile, --startupfile and --kernelfile command-line options in \"%s\" line %d", buildfile_pathname, lineno); |
- | |
1335 | if ((sep = strchr (value, ',')) != NULL) // do we have a comma separating (optional) processor and boot file name ? |
- | |
1336 | { |
- | |
1337 | *sep = 0; |
- | |
1338 | strcpy_s (image_processor, sizeof (image_processor), value); // save processor |
- | |
1339 | update_MKIFS_PATH (image_processor); |
- | |
1340 | value = sep + 1; |
- | |
1341 | } |
- | |
1342 | //sprintf (image_bootfile, "%s/%s/boot/sys/%s.boot", QNX_TARGET, image_processor, value); // save preboot file name (TODO: we should search in MKIFS_PATH instead of this. Not important.) |
- | |
1343 | //strcpy (image_bootfile, bootfile_pathname); // FIXME: HACK |
- | |
1344 | if (stat (bootfile_pathname, &stat_buf) != 0) |
- | |
1345 | DIE_WITH_EXITCODE (1, "unable to stat the boot file \"%s\" specified in \"%s\" line %d: %s", bootfile_pathname, buildfile_pathname, lineno, strerror (errno)); |
- | |
1346 | bootfile_size = stat_buf.st_size; // save preboot file size |
- | |
1347 | LOG_INFO ("processor \"%s\" bootfile \"%s\"\n", image_processor, bootfile_pathname); |
- | |
1348 | #if 1 |
- | |
1349 | // ###################################################################################################################################################################################################################################### |
- | |
1350 | // # FIXME: figure out how to re-create it: linker call involved |
- | |
1351 | // # $ x86_64-pc-nto-qnx8.0.0-ld --sysroot=${QNX_TARGET}/x86_64/ -T${QNX_TARGET}/x86_64/lib/nto.link --section-start .text=0xffff800000001000 --no-relax ${QNX_TARGET}/x86_64/boot/sys/procnto-smp-instr -o procnto-smp-instr.sym.UNSTRIPPED |
- | |
1352 | // ###################################################################################################################################################################################################################################### |
- | |
1353 | // if (!Buffer_ReadFromFile (&entry_parms.data, kernelfile_pathname)) |
- | |
1354 | // DIE_WITH_EXITCODE (1, "unable to read precompiled kernel file \"%s\" specified in --kernelfile argument: %s", kernelfile_pathname, strerror (errno)); |
- | |
1355 | #else // nonworking |
- | |
1356 | strcpy (path_on_buildhost, "procnto-smp-instr"); |
- | |
1357 | #endif // nonworking |
- | |
1358 | } |
- | |
1359 | else if (strncmp (token, "mtime=", 6) == 0) { REACH_TOKEN_VALUE (); if (strcmp (value, "*") == 0) entry_parms.mtime = UINT32_MAX; else { |
- | |
1360 | // value *must* be "YYYY-MM-DD-HH:MM:SS" by specification |
- | |
1361 | memset (&utc_time, 0, sizeof (utc_time)); |
- | |
1362 | if (sscanf_s (value, "%u-%u-%u-%u:%u:%u", &utc_time.tm_year, &utc_time.tm_mon, &utc_time.tm_mday, &utc_time.tm_hour, &utc_time.tm_min, &utc_time.tm_sec) != 6) |
- | |
1363 | { |
- | |
1364 | LOG_WARNING ("syntax error in \"%s\" line %d: mtime specification not in YYYY-MM-DD-HH:MM:SS format (skipping)", buildfile_pathname, lineno); |
- | |
1365 | continue; // invalid attribute block, skip line |
- | |
1366 | } |
- | |
1367 | utc_time.tm_mon--; // convert month from [1-12] to [0-11] |
- | |
1368 | entry_parms.mtime = (uint32_t) mktime (&utc_time); |
- | |
1369 | } |
- | |
1370 | } |
- | |
1371 | else if (strcmp (token, "+script") == 0) { |
- | |
1372 | entry_parms.is_compiled_bootscript = true; |
- | |
1373 | ASSERT_WITH_ERRNO (Buffer_InitWithByteArray (&entry_parms.data, INITIAL_STARTUP_SCRIPT)); // FIXME: HACK until the script compiler is implemented |
- | |
1374 | should_discard_inline_contents = true; // remember we already have data (so as to discard the inline block's contents) |
- | |
1375 | } |
- | |
1376 | else if (strcmp (token, "-script") == 0) entry_parms.is_compiled_bootscript = false; |
- | |
1377 | else if (strcmp (token, "+followlink") == 0) entry_parms.should_follow_symlinks = true; |
- | |
1378 | else if (strcmp (token, "-followlink") == 0) entry_parms.should_follow_symlinks = false; |
- | |
1379 | else if (strcmp (token, "+autolink") == 0) entry_parms.should_autosymlink_dylib = true; |
- | |
1380 | else if (strcmp (token, "-autolink") == 0) entry_parms.should_autosymlink_dylib = false; |
- | |
1381 | else if (strcmp (token, "+keeplinked") == 0) entry_parms.should_keep_ld_output = true; |
- | |
1382 | else if (strcmp (token, "-keeplinked") == 0) entry_parms.should_keep_ld_output = false; |
- | |
1383 | else LOG_WARNING ("unimplemented attribute in \"%s\" line %d: '%s'", buildfile_pathname, lineno, token); |
- | |
1384 | #undef REACH_TOKEN_VALUE |
- | |
1385 | - | ||
1386 | token = strtok_r (NULL, RECORD_SEP, &ctx); // proceed to next attribute token |
- | |
1387 | } |
- | |
1388 | - | ||
1389 | line_ptr++; // reach the next character |
- | |
1390 | while ((*line_ptr != 0) && isspace (*line_ptr)) |
- | |
1391 | line_ptr++; // skip leading spaces |
- | |
1392 | - | ||
1393 | // are we at the end of the line ? if so, it means the attribute values that are set should become the default |
- | |
1394 | if ((*line_ptr == 0) || (*line_ptr == '#')) |
- | |
1395 | { |
- | |
1396 | #define APPLY_DEFAULT_ATTR_NUM(attr,descr,fmt) do { if (entry_parms.attr != default_parms.attr) { \ |
- | |
1397 | LOG_INFO ("changing default " descr " from " fmt " to " fmt " by attribute at \"%s\" line %d", default_parms.attr, entry_parms.attr, buildfile_pathname, lineno); \ |
- | |
1398 | default_parms.attr = entry_parms.attr; \ |
- | |
1399 | } } while (0) |
- | |
1400 | #define APPLY_DEFAULT_ATTR_STR(attr,descr,fmt) do { if (strcmp (entry_parms.attr, default_parms.attr) != 0) { \ |
- | |
1401 | LOG_INFO ("changing default " descr " from " fmt " to " fmt " by attribute at \"%s\" line %d", default_parms.attr, entry_parms.attr, buildfile_pathname, lineno); \ |
- | |
1402 | strcpy_s (default_parms.attr, sizeof (default_parms.attr), entry_parms.attr); \ |
- | |
1403 | } } while (0) |
- | |
1404 | APPLY_DEFAULT_ATTR_NUM (dperms, "directory permissions", "0%o"); |
- | |
1405 | APPLY_DEFAULT_ATTR_NUM (perms, "file permissions", "0%o"); |
- | |
1406 | APPLY_DEFAULT_ATTR_NUM (uid, "owner ID", "%d"); |
- | |
1407 | APPLY_DEFAULT_ATTR_NUM (gid, "group ID", "%d"); |
- | |
1408 | APPLY_DEFAULT_ATTR_NUM (st_mode, "inode type", "0%o"); |
- | |
1409 | APPLY_DEFAULT_ATTR_STR (prefix, "prefix", "\"%s\""); |
- | |
1410 | APPLY_DEFAULT_ATTR_NUM (is_compiled_bootscript, "compiled script state", "%d"); |
- | |
1411 | APPLY_DEFAULT_ATTR_NUM (should_follow_symlinks, "symlink resolution", "%d"); |
- | |
1412 | APPLY_DEFAULT_ATTR_NUM (should_autosymlink_dylib, "dylib canonical name symlinking", "%d"); |
- | |
1413 | APPLY_DEFAULT_ATTR_NUM (should_keep_ld_output, "linker output preservation", "%d"); |
- | |
1414 | #undef APPLY_DEFAULT_ATTR_STR |
- | |
1415 | #undef APPLY_DEFAULT_ATTR_NUM |
- | |
1416 | continue; // end of line reached, proceed to the next line |
- | |
1417 | } |
- | |
1418 | // end of attributes parsing |
- | |
1419 | } // end of "this line starts with an attributes block" |
- | |
1420 | - | ||
1421 | // there's data in this line. We expect a filename in the IFS. Read it and unescape escaped characters |
- | |
1422 | string_len = sprintf_s (path_in_ifs, sizeof (path_in_ifs), "%s", entry_parms.prefix); |
- | |
1423 | while ((string_len > 0) && (path_in_ifs[string_len - 1] == '/')) |
- | |
1424 | string_len--; // chop off any trailing slashes from prefix |
- | |
1425 | write_ptr = &path_in_ifs[string_len]; |
- | |
1426 | *write_ptr++ = '/'; // add ONE trailing slash |
- | |
1427 | specifiedpathname_start = write_ptr; // remember the specified pathname will start here |
- | |
1428 | is_quoted_context = (*line_ptr == '"'); |
- | |
1429 | if (is_quoted_context) |
- | |
1430 | line_ptr++; // skip a possible initial quote |
- | |
1431 | if (*line_ptr == '/') |
- | |
1432 | { |
- | |
1433 | LOG_WARNING ("paths in the IFS file should not begin with a leading '/' in \"%s\" line %d", buildfile_pathname, lineno); |
- | |
1434 | line_ptr++; // consistency check: paths in the IFS should not begin with a '/' |
- | |
1435 | } |
- | |
1436 | while ((*line_ptr != 0) && ((!is_quoted_context && (*line_ptr != '=') && !isspace (*line_ptr)) || (is_quoted_context && (*line_ptr == '"')))) |
- | |
1437 | { |
- | |
1438 | if (*line_ptr == '\\') |
- | |
1439 | { |
- | |
1440 |
|
1726 | free (current_line); |
1441 | *write_ptr++ = *line_ptr; // unescape characters that are escaped with '\' |
- | |
1442 | } |
- | |
1443 | else |
- | |
1444 |
|
1727 | current_line = strdup (line_buffer); |
1445 | line_ptr++; |
- | |
1446 | } |
- | |
1447 | *write_ptr = 0; // terminate the string |
- | |
1448 | if (is_quoted_context && (*line_ptr == '"')) |
- | |
1449 | line_ptr++; // skip a possible final quote |
- | |
1450 | - | ||
1451 | // we reached a space OR an equal sign |
- | |
1452 | while ((*line_ptr != 0) && isspace (*line_ptr)) |
- | |
1453 | line_ptr++; // skip optional spaces after the filename in the IFS |
- | |
1454 | - | ||
1455 | // do we have an equal sign ? |
- | |
1456 | if (*line_ptr == '=') // we must be creating either a directory or a file, do we have an equal sign ? |
- | |
1457 | { |
- | |
1458 | line_ptr++; // skip the equal sign |
- | |
1459 | while ((*line_ptr != 0) && isspace (*line_ptr)) |
- | |
1460 | line_ptr++; // skip optional spaces after the equal sign |
- | |
1461 | - | ||
1462 | if (*line_ptr == 0) |
- | |
1463 | { |
- | |
1464 | LOG_WARNING ("syntax error in \"%s\" line %d: missing data specification after equal sign (skipping)", buildfile_pathname, lineno); |
- | |
1465 | continue; // invalid symlink specification, skip line |
- | |
1466 | } |
- | |
1467 | - | ||
1468 | // read the host system's path, it may be either a path or a contents definition. Is it a content definition ? |
- | |
1469 | if (*line_ptr == '{') |
- | |
1470 | { |
- | |
1471 | allocated_size = 0; |
- | |
1472 | - | ||
1473 | line_ptr++; // skip the leading content definition |
- | |
1474 | is_escaped_char = false; |
- | |
1475 | for (;;) |
- | |
1476 | { |
- | |
1477 | read_char = fgetc (buildfile_fp); |
- | |
1478 | if (read_char == EOF) |
- | |
1479 | DIE_WITH_EXITCODE (1, "syntax error in \"%s\" line %d: unterminated contents block (end of file reached)", buildfile_pathname, lineno); // invalid contents block |
- | |
1480 | else if ((read_char == '\\') && !is_escaped_char) |
- | |
1481 | is_escaped_char = true; // remember the next char is escaped |
- | |
1482 | else if ((read_char == '}') && !is_escaped_char) |
- | |
1483 | break; // found an unescaped closing bracked, stop parsing |
- | |
1484 | else |
- | |
1485 | { |
- | |
1486 | is_escaped_char = false; // any other char, meaning the next one will not be escaped |
- | |
1487 | if (!should_discard_inline_contents) // only store the contents if we do NOT know the data yet |
- | |
1488 | { |
- | |
1489 | if (entry_parms.data.size == allocated_size) // reallocate in 4 kb blocks |
- | |
1490 | { |
- | |
1491 | reallocated_ptr = realloc (entry_parms.data.bytes, allocated_size + 4096); |
- | |
1492 |
|
1728 | ASSERT_WITH_ERRNO (current_line); |
1493 | entry_parms.data.bytes = reallocated_ptr; |
- | |
1494 | allocated_size += 4096; |
- | |
1495 | } |
- | |
1496 | entry_parms.data.bytes[entry_parms.data.size++] = read_char; |
- | |
1497 | } |
- | |
1498 | if (read_char == '\n') |
- | |
1499 | lineno++; // update line counter as we parse the inline content |
- | |
1500 | } |
- | |
1501 | } // end for |
- | |
1502 | } |
- | |
1503 | else // not a content definition between { brackets }, must be either a pathname on the build host, or the target of a symlink |
- | |
1504 | { |
- | |
1505 | is_quoted_context = (*line_ptr == '"'); |
- | |
1506 | if (is_quoted_context) |
- | |
1507 | line_ptr++; // skip a possible initial quote |
- | |
1508 | specifiedpathname_start = line_ptr; // remember where the specified pathname starts |
- | |
1509 | write_ptr = line_ptr; // now unescape all characters |
- | |
1510 | while ((*line_ptr != 0) && ((!is_quoted_context && !isspace (*line_ptr)) || (is_quoted_context && (*line_ptr == '"')))) |
- | |
1511 | { |
- | |
1512 | if (*line_ptr == '\\') |
- | |
1513 | { |
- | |
1514 | line_ptr++; |
- | |
1515 | *write_ptr++ = *line_ptr; // unescape characters that are escaped with '\' |
- | |
1516 | } |
- | |
1517 | else |
- | |
1518 | *write_ptr++ = *line_ptr; |
- | |
1519 | line_ptr++; |
- | |
1520 | } |
- | |
1521 | *write_ptr = 0; // terminate the string |
- | |
1522 | if (is_quoted_context && (*line_ptr == '"')) |
- | |
1523 |
|
1729 | lineno++; // keep track of current line number |
1524 | - | ||
1525 | if (S_ISLNK (entry_parms.st_mode)) // are we storing a symlink ? |
- | |
1526 | ASSERT_WITH_ERRNO (Buffer_InitWithCString (&entry_parms.data, specifiedpathname_start)); // if so, store the symlink target as the dirent's blob data |
- | |
1527 | else // it's a build host filesystem path |
- | |
1528 | strcpy_s (path_on_buildhost, sizeof (path_on_buildhost), specifiedpathname_start); // the path on the build host is given after the equal sign |
- | |
1529 | } |
- | |
1530 | } |
- | |
1531 | else // no equal sign, meaning the file will have the same name on the build host filesystem |
- | |
1532 | { |
- | |
1533 | // consistency check: symlinks MUST have an equal sign |
- | |
1534 | if (entry_parms.st_mode == S_IFLNK) |
- | |
1535 | { |
- | |
1536 | LOG_WARNING ("syntax error in \"%s\" line %d: missing equal sign and symlink target (skipping)", buildfile_pathname, lineno); |
- | |
1537 | continue; // invalid symlink specification, skip line |
- | |
1538 | } |
- | |
1539 | - | ||
1540 | strcpy_s (path_on_buildhost, sizeof (path_on_buildhost), specifiedpathname_start); // the path on the build host is the one specified |
- | |
1541 | sep = strrchr (specifiedpathname_start, '/'); |
- | |
1542 | if (sep != NULL) |
- | |
1543 | memmove (specifiedpathname_start, sep + 1, strlen (sep + 1) + 1); // the path in the IFS will be the BASENAME of the path specified (after the prefix) |
- | |
1544 | } |
- | |
1545 | - | ||
1546 | // now add this entry to the image filesystem |
- | |
1547 | if (S_ISDIR (entry_parms.st_mode)) |
- | |
1548 | entry_parms.st_mode |= entry_parms.dperms; |
- | |
1549 | else if (S_ISLNK (entry_parms.st_mode)) |
- | |
1550 | entry_parms.st_mode |= 0777; // NOTE: mkifs sets symlink permissions to rwxrwxrwx !? |
- | |
1551 | else // file or device node |
- | |
1552 | entry_parms.st_mode |= entry_parms.perms; |
- | |
1553 | - | ||
1554 | add_fsentry (&fsentries, &fsentry_count, &entry_parms, path_in_ifs, path_on_buildhost); // and add filesystem entry |
- | |
1555 | 1730 | ||
1556 | if (entry_parms.data.bytes != NULL) |
- | |
1557 | free (entry_parms.data.bytes); // if blob data was allocated, free it |
- | |
1558 | } |
1731 | } |
1559 | 1732 | ||
1560 | fclose (buildfile_fp); // finished parsing the build file |
1733 | fclose (buildfile_fp); // finished parsing the build file |
1561 | 1734 | ||
1562 | ////////////////////////////////// |
1735 | ////////////////////////////////// |