2 //hmmm: anything related to _stub_size should be kept, but that is where
3 // we need a redundant search mechanism that can't be fooled so easily
4 // by modifying exe; make a pattern that will be found and is the first
5 // place to start looking for manifest.
7 /*****************************************************************************\
9 * Name : bundle_creator *
10 * Author : Chris Koeritz *
12 *******************************************************************************
13 * Copyright (c) 2006-$now By Author. This program is free software; you can *
14 * redistribute it and/or modify it under the terms of the GNU General Public *
15 * License as published by the Free Software Foundation; either version 2 of *
16 * the License or (at your option) any later version. This is online at: *
17 * http://www.fsf.org/copyleft/gpl.html *
18 * Please send any updates to: fred@gruntose.com *
19 \*****************************************************************************/
21 #include "common_bundle.h"
23 #include <application/hoople_main.h>
24 #include <application/command_line.h>
25 #include <basis/array.h>
26 #include <basis/byte_array.h>
27 #include <basis/environment.h>
28 #include <configuration/application_configuration.h>
29 #include <configuration/ini_configurator.h>
30 #include <configuration/variable_tokenizer.h>
31 #include <filesystem/byte_filer.h>
32 #include <filesystem/directory.h>
33 #include <filesystem/filename.h>
34 #include <filesystem/file_time.h>
35 #include <loggers/console_logger.h>
36 #include <loggers/file_logger.h>
37 #include <processes/launch_process.h>
38 #include <structures/static_memory_gremlin.h>
39 #include <structures/string_table.h>
40 #include <textual/byte_formatter.h>
41 #include <textual/list_parsing.h>
42 #include <textual/parser_bits.h>
43 #include <timely/time_stamp.h>
52 using namespace application;
53 using namespace basis;
54 using namespace configuration;
55 using namespace filesystem;
56 using namespace loggers;
57 using namespace filesystem;
58 using namespace processes;
59 using namespace structures;
60 using namespace textual;
61 using namespace timely;
63 const int CHUNKING_SIZE = 256 * KILOBYTE;
64 // we'll read this big a chunk from a source file at a time.
66 const astring SUBVERSION_FOLDER = ".svn";
67 // we don't want to include this in a bundle.
69 #define BASE_LOG(to_print) program_wide_logger::get().log(to_print, ALWAYS_PRINT)
70 #define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger::get(), to_print)
72 //#define DEBUG_BUNDLER
73 // uncomment for noisy debugging version.
75 // returns the "retval" and mentions that this is a failure at "where".
76 #define FAIL_RETURN(retval, where) { \
77 LOG(astring("failure in ") + where + a_sprintf(", exit=%d", retval)); \
81 ////////////////////////////////////////////////////////////////////////////
83 bool true_value(const astring &value)
84 { return (!value.equal_to("0")) && (!value.equal_to("false")); }
86 ////////////////////////////////////////////////////////////////////////////
88 // this structure overrides the manifest_chunk by providing a source string.
90 struct bundled_chunk : manifest_chunk
92 astring _source; //!< where the file comes from on the source system.
93 virtual ~bundled_chunk() {}
96 ////////////////////////////////////////////////////////////////////////////
98 // main bundler class.
100 class bundle_creator : public application_shell
104 : application_shell(),
105 _app_name(filename(_global_argv[0]).basename()),
106 _bundle(NULL_POINTER), _stub_size(0), _keyword() {}
108 virtual ~bundle_creator() {
112 DEFINE_CLASS_NAME("bundle_creator");
113 virtual int execute();
114 int print_instructions();
116 astring determine_stub_file_and_validate();
117 //!< returns the stub file location if it could be successfully located.
119 int open_output_file();
120 //!< prepares the output file to be written into.
121 /*!< non-zero return indicates an error. */
124 //!< reads manifest definition specifying files in the bundle.
125 /*!< creates the list of bundle pieces. */
127 int write_stub_and_toc();
128 //!< stuffs the unpacker stub into output file and table of contents.
130 int bundle_sources();
131 //!< reads all of the input files and dumps them into the bundle.
134 //!< puts finishing touches on the output file and closes it.
137 //!< writes the offset position into the output file.
138 /*!< this happens at the specially marked location (muftiloc). */
140 int patch_recursive_target(const astring &source, const astring &target,
142 //!< processes the recursive target specified in "curr".
143 /*!< the manifest_index tells the function where the external caller
144 is currently working on the manifest. new items will appear just after
147 int recurse_into_dir(const astring &source, const astring &target,
149 //!< adds all files from "source" to our list, recurses on dirs.
151 int patch_wildcard_target(const astring &source, const astring &target,
153 //!< processes the wildcard bearing target specified in "curr".
154 /*!< any new source items will get dropped on the end of the manifest. */
156 int add_files_here(directory &dirndl, const astring &source,
157 const astring &target, int manifest_index);
158 //!< takes all the files found in "source" and adds them to manifest.
160 bool get_file_size(const astring &file, un_int &size, byte_array ×tamp);
161 //!< returns the file "size" and "timestamp" found for "file".
164 astring _app_name; //!< application name for this program.
165 astring _output_file; //!< what bundle file to create.
166 astring _manifest_file; //!< the manifest of what's included in bundle.
167 array<bundled_chunk> _manifest_list; //!< the parsed list of contents.
168 byte_filer *_bundle; //!< points at the bundled output file.
169 int _stub_size; //!< where the TOC will be located.
170 astring _keyword; // set if we were given a keyword on cmd line.
173 ////////////////////////////////////////////////////////////////////////////
175 int bundle_creator::print_instructions()
177 BASE_LOG(a_sprintf("\
178 %s: This program needs two parameters on the command line.\n\
179 The -o flag must point at the bundled output file to create. The -m flag\n\
180 must point at a valid manifest file that defines what will be packed into\n\
181 the output file. See the example manifest in the bundler example\n\
182 (in setup_src/bundle_example) for more information on the required file\n\
188 int bundle_creator::execute()
192 BASE_LOG(astring("starting file bundling at ") + time_stamp::notarize(false));
194 command_line cmds(_global_argc, _global_argv);
196 //BASE_LOG(astring("before starting, cmds has: ") + parser_bits::platform_eol_to_chars() + cmds.text_form());
199 if (cmds.get_value('?', temp)) return print_instructions();
200 if (cmds.get_value("?", temp)) return print_instructions();
201 if (!cmds.get_value('o', _output_file)) return print_instructions();
202 if (!cmds.get_value('m', _manifest_file)) return print_instructions();
204 if (filename(_output_file).exists()) {
205 BASE_LOG(a_sprintf("\
206 %s: The output file already exists. Please move it out of\n\
207 the way; this program will not overwrite existing files.\n",
212 if (!filename(_manifest_file).exists()) {
213 BASE_LOG(a_sprintf("\
214 %s: The manifest file does not exist. This program cannot do anything\n\
215 without a valid packing manifest.\n", _app_name.s()));
219 // test this early on so we don't waste time uselessly.
220 astring stub_file_okay = determine_stub_file_and_validate();
221 if (!stub_file_okay) {
222 BASE_LOG(a_sprintf("\
223 %s: The unpacking stub file does not exist (check binaries folder).\n\
224 Abandoning bundling process.\n", _app_name.s()));
228 // make sure we snag any keyword that was passed on the command line.
229 cmds.get_value("keyword", _keyword);
231 // first step is to provide some built-in variables that can be used to
232 // make the manifests less platform specific. this doesn't really help
233 // if you bundle it on linux and try to run it on windows. but either
234 // platform's resources can easily be made into a bundle with the same
237 environment::set("EXE_END", ""); // executable file ending.
238 environment::set("DLL_START", "lib"); // dll file prefix.
239 environment::set("DLL_END", ".so"); // dll file ending.
241 environment::set("EXE_END", ".exe");
242 environment::set("DLL_START", "");
243 environment::set("DLL_END", ".dll");
245 // specify a target variable on the source side so that we can operate in there,
246 // even if the bundle doesn't specify one. otherwise we can't run source side commands
247 // properly if the paths are based on TARGET (like TMP often is).
248 environment::set("TARGET", environment::TMP());
251 if ( (ret = read_manifest()) ) FAIL_RETURN(ret, "reading manifest");
252 // read manifest to build list of what's what.
253 if ( (ret = open_output_file()) ) FAIL_RETURN(ret, "opening output file");
254 // open up our output file for the bundled chunks.
255 if ( (ret = write_stub_and_toc()) ) FAIL_RETURN(ret, "writing stub and TOC");
256 // writes the stub unpacker application and the table of contents to the
258 if ( (ret = bundle_sources()) ) FAIL_RETURN(ret, "bundling source files");
259 // stuff all the source files into the output bundle.
260 if ( (ret = finalize_file()) ) FAIL_RETURN(ret, "finalizing file");
261 // finishes with the file and closes it up.
262 if ( (ret = write_offset()) ) FAIL_RETURN(ret, "writing offset");
263 // stores the offset of the TOC into the output file in a special location
264 // that is delineated by a known keyword (muftiloc) and which should only
265 // exist in the file in one location.
270 int bundle_creator::open_output_file()
272 FUNCDEF("open_output_file");
273 _bundle = new byte_filer(_output_file, "wb");
274 if (!_bundle->good()) {
275 LOG(astring("failed to open the output file: ") + _output_file);
281 bool bundle_creator::get_file_size(const astring &infile, un_int &size,
282 byte_array &time_stamp)
284 FUNCDEF("get_file_size");
286 // access the source file to get its size.
287 byte_filer source_file(infile, "rb");
288 if (!source_file.good()) {
289 LOG(astring("could not access the file for size check: ") + infile);
292 size = int(source_file.length());
293 file_time tim(infile);
294 tim.pack(time_stamp);
298 int bundle_creator::add_files_here(directory &dirndl, const astring &source,
299 const astring &target, int manifest_index)
301 FUNCDEF("add_files_here");
302 for (int i = 0; i < dirndl.files().length(); i++) {
303 astring curry = dirndl.files()[i];
304 // skip .svn folders and contents.
305 if (curry.contains(SUBVERSION_FOLDER)) continue;
306 //hmmm: this could be a much nicer generalized file exclusion list.
308 //LOG(astring("file is: ") + curry);
309 bundled_chunk new_guy;
310 new_guy._source = source + "/" + curry; // the original full path to it.
311 new_guy._payload = target + "/" + curry;
312 new_guy._keywords = _manifest_list[manifest_index]._keywords;
313 // copy the flags from the parent, so we don't forget options.
314 new_guy._flags = _manifest_list[manifest_index]._flags;
315 // remove some flags that make no sense for the new guy.
316 new_guy._flags &= ~RECURSIVE_SRC;
318 //LOG(a_sprintf("adding: source=%s targ=%s", new_guy._source.s(), new_guy._payload.s()));
319 bool okaysize = get_file_size(new_guy._source, new_guy._size, new_guy.c_filetime);
320 if (!okaysize || (new_guy._size < 0) ) {
321 LOG(astring("failed to get file size for ") + new_guy._source);
325 _manifest_list.insert(manifest_index + 1, 1);
326 _manifest_list[manifest_index + 1] = new_guy;
331 int bundle_creator::recurse_into_dir(const astring &source,
332 const astring &target, int manifest_index)
334 FUNCDEF("recurse_into_dir");
335 //LOG(astring("src=") + source + " dest=" + target);
337 // we won't include the subversion folder.
338 if (source.contains(SUBVERSION_FOLDER)) return 0;
340 string_array dirs; // culled from the directory listing.
342 // don't pay for the directory object on the recursive invocation stack;
343 // just have what we need on the stack (the directory list).
344 directory dirndl(source);
345 //check dir for goodness!
346 int ret = add_files_here(dirndl, source, target, manifest_index);
347 // add in just the files that were found.
349 // this is a failure, but the function complains about it already.
352 dirs = dirndl.directories();
355 //LOG("now scanning directories...");
357 // now scan across the directories we found.
358 for (int i = 0; i < dirs.length(); i++) {
360 //LOG(astring("curr dir is ") + s);
361 int ret = recurse_into_dir(source + "/" + s, target + "/"
362 + s, manifest_index);
363 if (ret != 0) return ret; // bail out.
369 int bundle_creator::patch_recursive_target(const astring &source,
370 const astring &target, int manifest_index)
372 FUNCDEF("patch_recursive_target");
373 //LOG(astring("patch recurs src=") + source + " targ=" + target);
374 return recurse_into_dir(source, target, manifest_index);
377 int bundle_creator::patch_wildcard_target(const astring &source,
378 const astring &target, int manifest_index)
380 FUNCDEF("patch_wildcard_target");
381 // find the last slash. the rest is our wildcard component.
382 int src_end = source.end();
383 int slash_indy = source.find('/', src_end, true);
384 astring real_source = source.substring(0, slash_indy - 1);
385 astring wild_pat = source.substring(slash_indy + 1, src_end);
386 //BASE_LOG(astring("got src=") + real_source + " wildpat=" + wild_pat);
388 directory dirndl(real_source, wild_pat.s());
389 //check dir for goodness!
390 int ret = add_files_here(dirndl, real_source, target, manifest_index);
392 // this is a failure, but the function complains about it already.
399 int bundle_creator::read_manifest()
401 FUNCDEF("read_manifest");
402 ini_configurator ini(_manifest_file, configurator::RETURN_ONLY);
404 bool worked = ini.get_section("toc", toc);
406 LOG(astring("failed to read TOC section in manifest:\n") + _manifest_file
407 + "\ndoes that file exist?");
411 //hmmm: make a class member.
412 file_logger noisy_logfile(application_configuration::make_logfile_name
413 ("bundle_creator_activity.log"));
414 noisy_logfile.log(astring('-', 76));
415 noisy_logfile.log(astring("Bundling starts at ") + time_stamp::notarize(false));
417 // add enough items in the list for our number of sections.
418 _manifest_list.insert(0, toc.symbols());
419 astring value; // temporary string used below.
420 int final_return = 0; // if non-zero, an error occurred.
422 #define BAIL(retval) \
423 final_return = retval; \
425 _manifest_list.zap(i, i); \
429 for (int i = 0; i < toc.symbols(); i++) {
430 // read all the info in this section and store it into our list.
431 astring section_name = toc.name(i);
432 section_name.strip_spaces(astring::FROM_FRONT);
433 if (section_name[0] == '#') {
434 //hmmm: this looks a bit familiar from bail macro above. abstract out?
436 _manifest_list.zap(i, i);
438 continue; // skip comments.
441 // check for any keywords on the section. these are still needed for
442 // variables, which otherwise would skip the rest of the field checks.
443 if (ini.get(section_name, "keyword", value)) {
444 ///LOG(astring("into keyword processing--value held is ") + value);
446 bool worked = list_parsing::parse_csv_line(value, keys);
448 LOG(astring("failed to parse keywords for section ")
449 + section_name + " in " + _manifest_file);
452 ///LOG(astring("parsed list is ") + keys.text_form());
453 _manifest_list[i]._keywords = keys;
455 list_parsing::create_csv_line(_manifest_list[i]._keywords, dumped);
456 noisy_logfile.log(section_name + " keywords: " + dumped);
459 if (ini.get(section_name, "variable", value)) {
460 // this is a variable assignment. it is the only thing we care about
461 // for this section, so the rest is ignored.
462 variable_tokenizer zohre;
464 if (zohre.symbols() < 1) {
465 LOG(astring("failed to parse a variable statement from ") + value);
468 _manifest_list[i]._flags = SET_VARIABLE; // not orred, just this.
469 // set the two parts of our variable.
470 _manifest_list[i]._payload = zohre.table().name(0);
471 _manifest_list[i]._parms = zohre.table()[0];
472 BASE_LOG(astring("will set ") + _manifest_list[i]._payload + " = "
473 + _manifest_list[i]._parms);
474 astring new_value = parser_bits::substitute_env_vars(_manifest_list[i]._parms);
475 environment::set(_manifest_list[i]._payload, new_value);
478 BASE_LOG(astring("** variable ") + _manifest_list[i]._payload + " should have value=" + new_value);
479 BASE_LOG(astring("** variable ") + _manifest_list[i]._payload + " now does have value=" + environment::get(_manifest_list[i]._payload));
483 } else if (ini.get(section_name, "assert_defined", value)) {
484 // they are just asking for a variable test, to see if a variable
485 // that the installer needs is actually defined at unpacking time.
486 _manifest_list[i]._payload = value;
487 _manifest_list[i]._flags = TEST_VARIABLE_DEFINED;
488 BASE_LOG(astring("will test ") + _manifest_list[i]._payload + " is "
489 + "defined at unpacking time.");
493 if (!ini.get(section_name, "source", _manifest_list[i]._source)) {
494 // check whether they told us not to pack and it's executable.
495 bool okay_to_omit_source = false;
497 if (ini.get(section_name, "no_pack", value)
498 && ini.get(section_name, "exec_target", value2) ) {
499 if (true_value(value) && true_value(value2)) {
500 // this type of section doesn't need source declared.
501 okay_to_omit_source = true;
504 if (!okay_to_omit_source) {
505 LOG(astring("failed to read the source entry for section ")
506 + section_name + " in " + _manifest_file);
510 // fix meshugener backslashes so we can count on the slash direction.
511 _manifest_list[i]._source.replace_all('\\', '/');
513 if (!ini.get(section_name, "target", _manifest_list[i]._payload)) {
514 // check whether they told us not to pack and it's executable.
515 bool okay_to_omit_target = false;
517 if (ini.get(section_name, "no_pack", value)
518 && ini.get(section_name, "exec_source", value2) ) {
519 if (true_value(value) && true_value(value2)) {
520 // this type of section doesn't need target declared.
521 okay_to_omit_target = true;
524 if (!okay_to_omit_target) {
525 LOG(astring("failed to read the target entry for section ")
526 + section_name + " in " + _manifest_file);
530 // fix backslashes in target also.
531 _manifest_list[i]._payload.replace_all('\\', '/');
533 // capture any parameters they have specified for exec or other options.
534 if (ini.get(section_name, "parms", value)) {
535 _manifest_list[i]._parms = value;
537 BASE_LOG(astring("got parms for ") + section_name + " as: " + value);
539 if (value[0] != '"') {
540 // repair the string if we're running on windows.
541 _manifest_list[i]._parms = astring("\"") + value + "\"";
543 noisy_logfile.log(section_name + " parms: " + _manifest_list[i]._parms);
546 // check for the ignore errors flag.
547 if (ini.get(section_name, "error_okay", value)) {
548 if (true_value(value))
549 _manifest_list[i]._flags |= IGNORE_ERRORS;
552 // see if they are saying not to overwrite the target file.
553 if (ini.get(section_name, "no_replace", value)) {
554 if (true_value(value))
555 _manifest_list[i]._flags |= NO_OVERWRITE;
558 // test whether they are saying not to complain about a failure with
559 // our normal pop-up dialog (on winders).
560 if (ini.get(section_name, "quiet", value)) {
561 if (true_value(value))
562 _manifest_list[i]._flags |= QUIET_FAILURE;
565 // did they want a backup of the original to be made, instead of
566 // just overwriting the file?
567 if (ini.get(section_name, "make_backup", value)) {
568 if (true_value(value))
569 _manifest_list[i]._flags |= MAKE_BACKUP_FILE;
572 // look for our recursion flag.
573 if (ini.get(section_name, "recurse", value)) {
574 if (true_value(value))
575 _manifest_list[i]._flags |= RECURSIVE_SRC;
577 // the options here are only appropriate when the target is NOT set to
580 if (ini.get(section_name, "no_pack", value)) {
581 // allow either side to not be required if this is an executable.
582 if (true_value(value))
583 _manifest_list[i]._flags |= OMIT_PACKING;
586 // check if they have specified a source side executable.
587 if (ini.get(section_name, "exec_source", value)) {
588 if (true_value(value)) {
589 _manifest_list[i]._flags |= SOURCE_EXECUTE;
592 // check if they have specified a target side executable. this is
593 // mutually exclusive with a source side exec.
594 if (ini.get(section_name, "exec_target", value)) {
595 if (true_value(value))
596 _manifest_list[i]._flags |= TARGET_EXECUTE;
601 // replace environment variables in the source now...
602 _manifest_list[i]._source = parser_bits::substitute_env_vars
603 (_manifest_list[i]._source, false);
605 // look for wildcards in the source.
606 int indy = _manifest_list[i]._source.find("*");
608 // see if they specified a keyword on the command line and if this matches.
609 // if not we need to abandon this item.
610 if (!!_keyword && !_manifest_list[i]._keywords.member(_keyword)) {
611 // their keyword choice didn't match what we were told to use.
612 noisy_logfile.log(astring("skipping ") + _manifest_list[i]._payload
613 + " file check; doesn't match keyword \"" + _keyword + "\"");
617 // we only access the source file here if it's finalized. we can't do
618 // this if the target is supposed to be recursive or if it's got a wildcard
620 if (!(_manifest_list[i]._flags & RECURSIVE_SRC) && negative(indy)
621 && !(_manifest_list[i]._flags & OMIT_PACKING) ) {
622 // access the source file to get its size.
623 byte_filer source_file(_manifest_list[i]._source, "rb");
624 if (!source_file.good()) {
625 LOG(astring("could not access the source file for bundling: ")
626 + _manifest_list[i]._source);
629 bool okaysize = get_file_size(_manifest_list[i]._source,
630 _manifest_list[i]._size, _manifest_list[i].c_filetime);
631 if (!okaysize || (_manifest_list[i]._size < 0) ) {
632 // this is a failure, but the function complains about it already.
638 // patch the manifest list for wildcards and recursive sources.
639 for (int i = 0; i < _manifest_list.length(); i++) {
640 bundled_chunk curr = _manifest_list[i];
642 if (!!_keyword && !curr._keywords.member(_keyword)) {
643 // this item's keyword doesn't match the one we were given, so skip it.
644 noisy_logfile.log(astring("zapping entry for ") + curr._payload
645 + "; doesn't match keyword \"" + _keyword + "\"");
646 _manifest_list.zap(i, i);
647 i--; // skip back since we eliminated an index.
651 if (curr._flags & SET_VARIABLE) {
652 // we're done working on this.
654 } else if (curr._flags & TEST_VARIABLE_DEFINED) {
655 // this also requires no further effort.
657 } else if (curr._flags & RECURSIVE_SRC) {
658 // handle a recursive style target.
659 int star_indy = curr._source.find("*");
660 if (non_negative(star_indy)) {
661 // this is currently illegal. we don't allow recursion + wildcards.
662 LOG(astring("illegal combination of recursion and wildcard: ")
666 // handle the recursive guy.
667 int ret = patch_recursive_target(curr._source, curr._payload, i);
669 LOG(astring("failed during packing of recursive source: ")
673 // take this item out of the picture, since all contents got included.
674 _manifest_list.zap(i, i);
675 i--; // skip back since we eliminated an index.
677 } else if (curr._flags & SOURCE_EXECUTE) {
678 // we have massaged the current manifest chunk as much as we can, so now
679 // we will execute the source item if that was specified.
680 BASE_LOG(astring("launching ") + curr._source);
682 curr._parms = parser_bits::substitute_env_vars(curr._parms, false);
683 BASE_LOG(astring("\tparameters ") + curr._parms);
685 BASE_LOG(astring('-', 76));
687 basis::un_int retval = launch_process::run(curr._source, curr._parms,
688 launch_process::AWAIT_APP_EXIT, kid);
690 LOG(astring("failed to launch process, source=") + curr._source
691 + ", with parms " + curr._parms);
692 if (! (curr._flags & IGNORE_ERRORS) ) {
696 BASE_LOG(astring('-', 76));
697 if (curr._flags & OMIT_PACKING) {
698 // this one shouldn't be included in the package.
699 _manifest_list.zap(i, i);
700 i--; // skip back since we eliminated an index.
704 // check for a wildcard.
705 int star_indy = curr._source.find("*");
706 if (negative(star_indy)) continue; // simple targets are boring.
707 // this does have a wildcard in it. let's make sure it's in the right
708 // place for a wildcard in our scheme.
709 int slash_indy = curr._source.find('/', curr._source.end(), true);
710 if (star_indy < slash_indy) {
711 BASE_LOG(astring("illegal wildcard placement in ") + curr._source);
712 BASE_LOG(astring(" (the wildcard must be in the last component of the path)"));
715 // handle the wildcarded source.
716 int ret = patch_wildcard_target(curr._source, curr._payload, i);
718 LOG(astring("failed during packing of wildcarded source: ")
722 _manifest_list.zap(i, i);
723 i--; // skip back since we eliminated an index.
730 // we had a successful run so we can print this stuff out.
731 LOG("read the following info from manifest:");
732 for (int i = 0; i < _manifest_list.length(); i++) {
733 bundled_chunk &curr = _manifest_list[i];
734 BASE_LOG(a_sprintf("(%d) size %d, %s => %s", i, curr._size,
735 curr._source.s(), curr._payload.s()));
743 astring bundle_creator::determine_stub_file_and_validate()
745 FUNCDEF("determine_stub_file_and_validate");
746 // define our location to find the unpacking stub program.
747 //hmmm: make this a command line parameter.
749 astring stub_filename("unpacker_stub");
752 astring stub_filename("unpacker_stub.exe");
754 astring repo_dir = "$RUNTIME_PATH";
755 astring stub_file = parser_bits::substitute_env_vars
756 (repo_dir + "/binaries/" + stub_filename, false);
757 if (!filename(stub_file).exists()) {
758 // we needed to find that to build the bundle.
759 LOG(astring("could not find unpacking stub file at: ") + stub_file);
760 return astring::empty_string();
765 int bundle_creator::write_stub_and_toc()
767 FUNCDEF("write_stub_and_toc");
769 astring stub_file = determine_stub_file_and_validate();
770 if (!stub_file) return 1;
772 // make sure the stub is accessible.
773 byte_filer stubby(stub_file, "rb");
774 if (!stubby.good()) {
775 FAIL_RETURN(80, astring("could not read the unpacking stub at: ") + stub_file);
777 _stub_size = int(stubby.length()); // get the stub size for later reference.
778 byte_array whole_stub;
779 stubby.read(whole_stub, _stub_size + 100);
781 _bundle->write(whole_stub);
783 byte_array packed_toc_len;
784 structures::obscure_attach(packed_toc_len, _manifest_list.length());
785 int ret = _bundle->write(packed_toc_len);
787 LOG(astring("could not write the TOC length to the bundle: ")
792 // dump out the manifest list in our defined format.
793 for (int i = 0; i < _manifest_list.length(); i++) {
794 bundled_chunk &curr = _manifest_list[i];
795 //LOG(a_sprintf("flag %d is %d", i, curr._flags));
798 if (_bundle->write(chunk) <= 0) {
799 LOG(a_sprintf("could not write item #%d [%s] to the bundle: ", i,
809 int bundle_creator::bundle_sources()
811 FUNCDEF("bundle_sources");
812 // go through all the source files and append them to the bundled output.
813 file_logger noisy_logfile(application_configuration::make_logfile_name
814 ("bundle_creator_activity.log"));
815 for (int i = 0; i < _manifest_list.length(); i++) {
816 bundled_chunk &curr = _manifest_list[i];
818 if (curr._flags & SET_VARIABLE) {
819 // all we need to do is keep this in the manifest.
820 noisy_logfile.log(astring("bundling: variable setting ") + curr._payload
821 + "=" + curr._parms);
823 } else if (curr._flags & TEST_VARIABLE_DEFINED) {
824 // just remember to test this when running the unpack.
825 noisy_logfile.log(astring("bundling: test variable ") + curr._payload
828 } else if (curr._flags & OMIT_PACKING) {
829 // this one shouldn't be included in the package.
833 noisy_logfile.log(astring("bundling: ") + curr._source);
834 byte_filer source(curr._source, "rb");
835 if (!source.good()) {
836 LOG(a_sprintf("could not read item #%d for the bundle: \"", i)
837 + curr._source + "\"");
841 byte_array compressed(256 * KILOBYTE); // expand the buffer to start with.
842 byte_array temp; // temporary read buffer.
844 // chew on the file a chunk at a time. this allows us to easily handle
845 // arbitrarily large files rather than reading their entirety into memory.
846 int total_written = 0;
848 int ret = source.read(temp, CHUNKING_SIZE);
850 LOG(a_sprintf("failed while reading item #%d: ", i) + curr._source);
853 total_written += ret; // add in what we expect to write.
854 // skip compressing if there's no data.
856 bool null_chunk = false;
861 compressed.reset(int(0.1 * ret) + ret + KILOBYTE);
862 // provide some extra space as per zlib instructions. we're giving it
863 // way more than they request.
864 destlen = compressed.length();
865 // pack the chunks first so we can know sizes needed.
866 int comp_ret = compress(compressed.access(), &destlen, temp.observe(),
868 if (comp_ret != Z_OK) {
869 LOG(a_sprintf("failed while compressing item #%d: ", i)
873 compressed.zap(destlen, compressed.length() - 1);
875 byte_array just_sizes;
876 structures::obscure_attach(just_sizes, temp.length());
877 // add in the real size.
878 structures::obscure_attach(just_sizes, int(destlen));
879 // add in the packed size.
880 ret = _bundle->write(just_sizes);
882 LOG(a_sprintf("failed while writing sizes for item #%d: ", i)
887 ret = _bundle->write(compressed);
889 LOG(a_sprintf("failed while writing item #%d: ", i) + curr._source);
891 } else if (ret != compressed.length()) {
892 LOG(a_sprintf("wrote different size for item #%d (tried %d, "
893 "wrote %d): ", i, compressed.length(), ret) + curr._source);
897 } while (!source.eof());
898 //hmmm: very common code to above size writing.
899 byte_array just_sizes;
900 structures::obscure_attach(just_sizes, -1);
901 structures::obscure_attach(just_sizes, -1);
902 int ret = _bundle->write(just_sizes);
904 LOG(a_sprintf("failed while writing sentinel of item #%d: ", i)
909 if (total_written != curr._size) {
910 LOG(a_sprintf("size (%d) disagrees with initial size (%d) for "
911 "item #%d: ", total_written, curr._size, i) + curr._source);
914 noisy_logfile.log(astring("Bundling run ends at ") + time_stamp::notarize(false));
915 noisy_logfile.log(astring('-', 76));
920 int bundle_creator::finalize_file()
926 int bundle_creator::write_offset()
928 FUNCDEF("write_offset");
929 byte_filer bun(_output_file, "r+b"); // open the file for updating.
931 astring magic_string("muftiloc"); // our sentinel string.
932 astring temp_string; // data from the file.
935 // find the telltale text in the file.
936 bool found_it = false; // we'll set this to true if we see the string.
937 int location = 0; // where the sentinel's end is.
938 for (int i = 0; i < magic_string.length(); i++) {
939 int ret = bun.read(temp_string, 1);
941 if (temp_string[0] != magic_string[i]) break; // no match.
942 if (i == magic_string.end()) {
943 // we found a match to our string!
945 location = int(bun.tell());
946 //LOG(a_sprintf("found the sentinel in the file! posn=%d", location));
949 if (!found_it) continue; // keep reading.
951 byte_array packed_offset;
952 structures::obscure_attach(packed_offset, _stub_size);
953 //LOG(astring("pattern of len is:\n") + byte_format::text_dump(packed_offset));
954 // write the offset into the current position, which should be just after
955 // the sentinel's location.
956 bun.write(packed_offset);
957 //LOG(a_sprintf("wrote manifest offset before posn=%d", bun.tell()));
958 break; // done with looking for that pattern.
960 bun.close(); // completely finished now.
962 chmod(_output_file.s(), 0766);
963 // make sure it's an executable file when we're done with it.
965 BASE_LOG(astring("done file bundling at ") + time_stamp::notarize(false));
970 ////////////////////////////////////////////////////////////////////////////
972 HOOPLE_MAIN(bundle_creator, )
974 #ifdef __BUILD_STATIC_APPLICATION__
975 // static dependencies found by buildor_gen_deps.sh:
976 #include <application/application_shell.cpp>
977 #include <application/command_line.cpp>
978 #include <basis/astring.cpp>
979 #include <basis/common_outcomes.cpp>
980 #include <basis/environment.cpp>
981 #include <basis/mutex.cpp>
982 #include <basis/utf_conversion.cpp>
983 #include <configuration/application_configuration.cpp>
984 #include <configuration/configurator.cpp>
985 #include <configuration/ini_configurator.cpp>
986 #include <configuration/ini_parser.cpp>
987 #include <configuration/table_configurator.cpp>
988 #include <configuration/variable_tokenizer.cpp>
989 #include <filesystem/byte_filer.cpp>
990 #include <filesystem/directory.cpp>
991 #include <filesystem/filename.cpp>
992 #include <filesystem/file_time.cpp>
993 #include <loggers/combo_logger.cpp>
994 #include <loggers/console_logger.cpp>
995 #include <loggers/critical_events.cpp>
996 #include <loggers/file_logger.cpp>
997 #include <loggers/program_wide_logger.cpp>
998 #include <processes/launch_process.cpp>
999 #include <structures/bit_vector.cpp>
1000 #include <structures/checksums.cpp>
1001 #include <structures/object_packers.cpp>
1002 #include <structures/static_memory_gremlin.cpp>
1003 #include <structures/string_hasher.cpp>
1004 #include <structures/string_table.cpp>
1005 #include <structures/version_record.cpp>
1006 #include <textual/byte_formatter.cpp>
1007 #include <textual/list_parsing.cpp>
1008 #include <textual/parser_bits.cpp>
1009 #include <textual/string_manipulation.cpp>
1010 #include <timely/earth_time.cpp>
1011 #include <timely/time_control.cpp>
1012 #include <timely/time_stamp.cpp>
1013 #endif // __BUILD_STATIC_APPLICATION__