unpacker_stub.cpp

Go to the documentation of this file.
00001 /*****************************************************************************\
00002 *                                                                             *
00003 *  Name   : unpacker stub program                                             *
00004 *  Author : Chris Koeritz                                                     *
00005 *                                                                             *
00006 *******************************************************************************
00007 * Copyright (c) 2006-$now By Author.  This program is free software; you can  *
00008 * redistribute it and/or modify it under the terms of the GNU General Public  *
00009 * License as published by the Free Software Foundation; either version 2 of   *
00010 * the License or (at your option) any later version.  This is online at:      *
00011 *     http://www.fsf.org/copyleft/gpl.html                                    *
00012 * Please send any updates to: fred@gruntose.com                               *
00013 \*****************************************************************************/
00014 
00015 #include "common_bundle.h"
00016 
00017 #include <basis/array.cpp>
00018 #include <basis/byte_array.h>
00019 #include <basis/guards.h>
00020 #include <basis/portable.h>
00021 #include <data_struct/string_table.h>
00022 #include <opsystem/application_shell.h>
00023 #include <opsystem/byte_filer.h>
00024 #include <opsystem/command_line.h>
00025 #include <loggers/console_logger.h>
00026 #include <opsystem/directory.h>
00027 #include <opsystem/filename.h>
00028 #include <loggers/file_logger.h>
00029 #include <opsystem/filetime.h>
00030 #include <opsystem/heavy_file_ops.h>
00031 #include <data_struct/static_memory_gremlin.h>
00032 #include <textual/parser_bits.h>
00033 #include <textual/tokenizer.h>
00034 #include <win_ext/window_classist.h>
00035 
00036 #include <stdio.h>
00037 #include <sys/stat.h>
00038 #include <zlib.h>
00039 #ifdef __UNIX__
00040   #include <utime.h>
00041 #endif
00042 #ifdef __WIN32__
00043   #include <direct.h>
00044   #include <io.h>
00045   #include <shlobj.h>
00046   #include <sys/utime.h>
00047 #endif
00048 
00049 const int CHUNKING_SIZE = 64 * KILOBYTE;
00050   // we'll read this big a chunk from a source file at a time.
00051 
00052 #define BASE_LOG(to_print) program_wide_logger().log(to_print)
00053 #define LOG(to_print) STAMPED_EMERGENCY_LOG(program_wide_logger(), to_print)
00054 
00055 //#define DEBUG_STUB
00056   // uncomment for noisier version.
00057 
00058 const char *ERROR_TITLE = "An Error Caused Incomplete Installation";
00059   // used in error messages as the title.
00060 
00062 
00063 class unpacker_stub : public application_shell
00064 {
00065 public:
00066   unpacker_stub()
00067     : application_shell("unpacker_stub"),
00068       _app_name(filename(__argv[0]).basename()) {}
00069   IMPLEMENT_CLASS_NAME("unpacker_stub");
00070 
00071   int print_instructions();
00072 
00073   virtual int execute();
00074 
00075 private:
00076   istring _app_name;
00077   array<manifest_chunk> _manifest;  
00078   string_table _variables;  
00079 };
00080 
00082 
00083 void show_message(const istring &msg, const istring &title)
00084 {
00085 #ifndef __WIN32__
00086   BASE_LOG(title);
00087   BASE_LOG(istring('-', title.length()));
00088   BASE_LOG(msg);
00089 #else
00090   MessageBox(0, to_unicode_temp(msg), to_unicode_temp(title),
00091       MB_OK | MB_ICONINFORMATION);
00092 #endif
00093 }
00094 
00095 int unpacker_stub::print_instructions()
00096 {
00097   isprintf msg("\
00098     %s: This program unpacks its contents into the locations\n\
00099  specified at packing time.  The --target flag can be used to specify a\n\
00100 different TARGET directory for the installation (for components that use\n\
00101 the default TARGET variable to specify their install folder).\n\
00102     One can also pass a --keyword flag to specify a keyword; the files in the\n\
00103 bundle that are marked with that keyword will be installed, but files that\n\
00104 are missing the keyword will not be.\n\
00105     Further, variables can be overridden on the command line in the\n\
00106 form: X=Y.\n\n\
00107 The line below uses all these parameters as an example:\n\n\
00108   %s --target c:\\Program Files\\gubernator --keyword dlls_only SILENT=true\n\
00109 \n\
00110 Additional Notes:\n\
00111 \n\
00112     One helpful variable is \"LOGDIR\".  This is where the unpacking log file\n\
00113 will be written to.  The default is \"~/logs\" (or \"$TMP/logs\" on win32)\n\
00114 until overridden.\n\
00115 \n", _app_name.s(), _app_name.s());
00116   show_message(msg, "Unpacking Instructions");
00117   return 12;
00118 }
00119 
00120 // the string embedded into the array is not mentioned anywhere else in this
00121 // program, which should allow the packer to find it and fix the manifest
00122 // size.  the keyword is: "muftiloc", and the first bytes that can be
00123 // overwritten are its beginning offset plus its length of 8 chars.  there
00124 // is room for 8 bytes after the tag, but currently the first 4 are used as
00125 // a 32 bit offset.
00126 byte MANIFEST_OFFSET_ARRAY[]
00127     = { 'm', 'u', 'f', 't', 'i', 'l', 'o', 'c', 0, 0, 0, 0, 0, 0, 0, 0 };
00128 
00129 int unpacker_stub::execute()
00130 {
00131 #ifdef ADMIN_CHECK
00132   #ifdef __WIN32__
00133     if (IsUserAnAdmin()) {
00134       ::MessageBox(0, to_unicode_temp("IS admin in bundler"), to_unicode_temp("bundler"), MB_OK);
00135     } else {
00136       ::MessageBox(0, to_unicode_temp("NOT admin in bundler"), to_unicode_temp("bundler"), MB_OK);
00137     }
00138   #endif
00139 #endif
00140 
00141   command_line cmds(__argc, __argv);
00142 
00143   int indy = 0;
00144   if (cmds.find('?', indy)) return print_instructions();
00145   if (cmds.find("?", indy)) return print_instructions();
00146 
00147   // make sure we provide the same services as the bundle creator for the
00148   // default set of variables.
00149 #ifndef __WIN32__
00150   portable::set_environ("EXE_END", "");  // executable file ending.
00151   portable::set_environ("DLL_START", "lib");  // dll file prefix.
00152   portable::set_environ("DLL_END", ".so");  // dll file ending.
00153 #else
00154   portable::set_environ("EXE_END", ".exe");
00155   portable::set_environ("DLL_START", "");
00156   portable::set_environ("DLL_END", ".dll");
00157 #endif
00158 
00159   // set TARGET directory if passed on the command line,
00160   bool provided_target = true;  // true if the command line specified target.
00161   istring target;
00162   cmds.get_value("target", target);
00163   // a bogus default is provided if they don't specify the destination.
00164   if (!target) {
00165     target = portable::env_string("TMP") + "/unbundled";
00166     provided_target = false;
00167   }
00168 //LOG(istring("target is now ") + target);
00169   portable::set_environ("TARGET", target);
00170 
00171   {
00172     istring logdir = portable::env_string("LOGDIR");
00173 #ifdef __WIN32__
00174     if (!logdir) {
00175       logdir = portable::env_string("TMP") + "/logs";
00176       portable::set_environ("LOGDIR", logdir);
00177     }
00178 #else
00179     if (!logdir) {
00180       istring homedir = portable::env_string("HOME");
00181       logdir = homedir + "/logs";
00182       portable::set_environ("LOGDIR", logdir);
00183     }
00184 #endif
00185   }
00186 
00187   istring keyword;  // set if we were given a keyword on cmd line.
00188   cmds.get_value("keyword", keyword);
00189 
00190   istring vars_set;  // we will document the variables we saw and show later.
00191 
00192   for (int x = 0; x < cmds.entries(); x++) {
00193     command_parameter curr = cmds.get(x);
00194     if (curr.type() != command_parameter::VALUE) continue;  // skip it.
00195     if (curr.text().find('=', 0) < 0) continue;  // no equals character.
00196     tokenizer t;
00197     t.parse(curr.text());
00198     if (!t.symbols()) continue;  // didn't parse right.
00199     istring var = t.table().name(0);
00200     istring value = t.table()[0];
00201     vars_set += istring("variable set: ") + var + "=" + value
00202         + log_base::platform_ending();
00203     _variables.add(var, value);
00204     portable::set_environ(var, value);
00205   }
00206 
00207   // get the most up to date version of the variable now.
00208   istring logdir = portable::env_string("LOGDIR");
00209 
00210   istring appname = filename(portable::application_name()).rootname();
00211   istring logname = logdir + "/" + appname + ".log";
00212   log_base *old_log = set_PW_logger_for_combo(logname);
00213   WHACK(old_log);
00214 
00215   BASE_LOG(istring('#', 76));
00216   BASE_LOG(appname + " command-line parameters:");
00217   BASE_LOG(cmds.text_form());
00218   BASE_LOG(istring('#', 76));
00219 
00220   BASE_LOG(vars_set);
00221 
00222 #ifdef __WIN32__
00223   // create a window so that installshield won't barf.  this is only needed
00224   // on windows when using this as a prerequisite for installshield.
00225   window_handle f_window = create_simplistic_window("temp_stubby_class",
00226       "stubby window title");
00227 #endif
00228 
00229   // read position for manifest offset out of our array.
00230   byte_array temp_packed(2 * sizeof(int), MANIFEST_OFFSET_ARRAY + 8);
00231   int manifest_offset;
00232   if (!basis::obscure_detach(temp_packed, manifest_offset)) {
00233     show_message(istring("could not read manifest offset in: ") + __argv[0],
00234         ERROR_TITLE);
00235     return 24;
00236   }
00237   
00238   filename this_exe(__argv[0]);
00239   if (!this_exe.exists()) {
00240     show_message(istring("could not access this exe image: ") + this_exe.raw(),
00241         ERROR_TITLE);
00242     return 23;
00243   }
00244 
00245   // start reading the manifest...
00246   byte_filer our_exe(this_exe, "rb");
00247   our_exe.seek(manifest_offset);  // go to where the manifest starts.
00248 
00249   // get number of chunks in manifest as the first bytes.
00250   if (our_exe.read(temp_packed, 2 * sizeof(int)) <= 0) {
00251     show_message(istring("could not read the manifest length in: ")
00252         + this_exe.raw(), ERROR_TITLE);
00253     return 26;
00254   }
00255   int item_count;
00256   basis::obscure_detach(temp_packed, item_count);
00257 //check result of detach!
00258   _manifest.insert(0, item_count);  // add enough spaces for our item list.
00259 
00260   // read each item definition out of the manifest now.
00261   for (int i = 0; i < item_count; i++) {
00262     manifest_chunk &curr = _manifest[i];
00263     bool worked = manifest_chunk::read_manifest(our_exe, curr);
00264     if (!worked) {
00265       show_message(isprintf("could not read chunk for item #%d [%s]: ", i,
00266           curr._target.s())
00267           + this_exe.raw(), ERROR_TITLE);
00268       return 86;
00269     }
00270   }
00271 
00272 #ifdef DEBUG_STUB
00273   LOG("read the following info from manifest:");
00274   istring temp = "size\ttarget\r\n";
00275   for (int i = 0; i < _manifest.length(); i++) {
00276     manifest_chunk &curr = _manifest[i];
00277     temp += isprintf("%d\t%s\r\n", curr._size, curr._target.s());
00278   }
00279   guards::alert_message(temp, "manifest contents");
00280 #endif
00281 
00282   // now we should be just after the last byte of the manifest, at the
00283   // first piece of data.  we should read each chunk of data out and store
00284   // it where it's supposed to go.
00285   for (int festdex = 0; festdex < _manifest.length(); festdex++) {
00286     manifest_chunk &curr = _manifest[festdex];
00287     int size_left = curr._size;
00288 
00289     // patch in our environment variables.
00290     curr._target = parser_bits::substitute_env_vars(curr._target, false);
00291     curr._parms = parser_bits::substitute_env_vars(curr._parms, false);
00292 #ifdef DEBUG_STUB
00293     BASE_LOG(istring("processing ") + curr._target
00294         + isprintf(", size=%d, flags=%d", curr._size, curr._flags));
00295     if (!!curr._parms)
00296       BASE_LOG(istring("   parms: ") + curr._parms);
00297 #endif
00298 
00299     // see if they specified a keyword on the command line.
00300     bool keyword_good = true;
00301     if (!!keyword && !curr._keywords.member(keyword)) {
00302       // their keyword choice didn't match what we were pickled with.
00303       keyword_good = false;
00304 //BASE_LOG(istring("skipping ") + curr._target + " for wrong keyword " + keyword);
00305     }
00306 
00307     if (curr._flags & SET_VARIABLE) {
00308       if (keyword_good) {
00309         // this is utterly different from a real target.  we just set a
00310         // variable and move on.
00311         if (provided_target && (curr._target == "TARGET") ) {
00312           BASE_LOG(istring("skipping ") + curr._target + "=" + curr._parms
00313               + ": was provided explicitly as " + target);
00314         } else if (_variables.find(curr._target)) {
00315           BASE_LOG(istring("skipping ") + curr._target + "=" + curr._parms
00316               + ": was provided on command line.");
00317         } else {
00318           BASE_LOG(istring("setting ") + curr._target + "=" + curr._parms);
00319           portable::set_environ(curr._target, curr._parms);
00320 
00321           // special code for changing logging directory midstream.
00322           if (curr._target == "LOGDIR") {
00323             istring logdir = curr._parms;
00324             istring appname = filename(portable::application_name()).rootname();
00325             istring logname = logdir + "/" + appname + ".log";
00326             log_base *old_log = set_PW_logger_for_combo(logname);
00327             WHACK(old_log);
00328           }
00329         }
00330       }
00331       continue;
00332     }
00333 
00334     if (! (curr._flags & OMIT_PACKING) ) {
00335       // this one has a payload, so install it now.
00336 
00337       // make sure that the directories needed are present for the outputs.
00338       filename target_dir = filename(curr._target).dirname();
00339       if (keyword_good && !target_dir.exists()
00340           && !directory::recursive_create(target_dir)) {
00341         LOG(isprintf("failed to create directory %s for item #%d: ",
00342             target_dir.raw().s(), festdex) + curr._target);
00343       }
00344 
00345       if (curr._flags & NO_OVERWRITE) {
00346         filename target_file(curr._target);
00347         if (target_file.exists()) {
00348           BASE_LOG(istring("not overwriting existing ") + curr._target);
00349           keyword_good = false;
00350         }
00351       }
00352 
00353       byte_filer *targo = NIL;
00354       if (keyword_good) targo = new byte_filer(curr._target, "wb");
00355       byte_array uncompressed(256 * KILOBYTE);  // fluff it out to begin with.
00356       byte_array temp(256 * KILOBYTE);
00357 
00358       bool first_read = true;
00359         // true if there haven't been any reads of the file before now.
00360       bool too_tiny_complaint_already = false;
00361         // becomes true if we complain about the file's size being larger than
00362         // expected.  this allows us to only complain once about each file.
00363 
00364       // read a chunk at a time out of our exe image and store it into the
00365       // target file.
00366       while (first_read || !our_exe.eof()) {
00367         first_read = false;
00368         int real_size = 0, packed_size = 0;
00369         // read in the real size from the file.
00370         bool worked = manifest_chunk::read_an_obscured_int(our_exe, real_size);
00371         if (!worked) {
00372           show_message(isprintf("failed while reading real size "
00373               "for item #%d: ", festdex) + curr._target, ERROR_TITLE);
00374           return 99;
00375         }
00376         // read in the packed size now.
00377         worked = manifest_chunk::read_an_obscured_int(our_exe, packed_size);
00378         if (!worked) {
00379           show_message(isprintf("failed while reading packed size "
00380               "for item #%d: ", festdex) + curr._target, ERROR_TITLE);
00381           return 99;
00382         }
00383 
00384         // make sure we don't eat the whole package--did the file end?
00385         if ( (real_size == -1) && (packed_size == -1) ) {
00386           // we've hit our sentinel; we've already unpacked all of this file.
00387           break;
00388         }
00389 
00390 #ifdef DEBUG_STUB
00391         BASE_LOG(isprintf("chunk packed_size=%d, real_size=%d", packed_size,
00392             real_size));
00393 #endif
00394 
00395         // now we know how big our next chunk is, so we can try reading it.
00396         if (packed_size) {
00397           int ret = our_exe.read(temp, packed_size);
00398           if (ret <= 0) {
00399             show_message(isprintf("failed while reading item #%d: ", festdex)
00400                 + curr._target, ERROR_TITLE);
00401             return 99;
00402           } else if (ret != packed_size) {
00403             show_message(isprintf("bad trouble ahead, item #%d had different "
00404                 " size on read (expected %d, got %d): ", festdex, packed_size,
00405                 ret) + curr._target, ERROR_TITLE);
00406           }
00407 
00408           uncompressed.reset(real_size + KILOBYTE);  // add some for paranoia.
00409           uLongf destlen = uncompressed.length();
00410           int uncomp_ret = uncompress(uncompressed.access(), &destlen,
00411               temp.observe(), packed_size);
00412           if (uncomp_ret != Z_OK) {
00413             show_message(isprintf("failed while uncompressing item #%d: ",
00414                 festdex) + curr._target, ERROR_TITLE);
00415             return 99;
00416           }
00417   
00418           if (int(destlen) != real_size) {
00419             LOG(isprintf("got a different unpacked size for item #%d: ",
00420                 festdex) + curr._target);
00421           }
00422 
00423           // update the remaining size for this data chunk.
00424           size_left -= real_size;
00425           if (size_left < 0) {
00426             if (!too_tiny_complaint_already) {
00427               LOG(isprintf("item #%d was larger than expected (non-fatal): ",
00428                   festdex) + curr._target);
00429               too_tiny_complaint_already = true;
00430             }
00431           }
00432           // toss the extra bytes out.
00433           uncompressed.zap(real_size, uncompressed.length() - 1);
00434 
00435           if (targo) {
00436             // stuff the data we read into the target file.
00437             ret = targo->write(uncompressed);
00438             if (ret != uncompressed.length()) {
00439               show_message(isprintf("failed while writing item #%d: ", festdex)
00440                   + curr._target, ERROR_TITLE);
00441               return 93;
00442             }
00443           }
00444         }
00445       }
00446       if (targo) targo->close();
00447       WHACK(targo);
00448       // the file's written, but now we slap it's old time on it too.
00449       file_time t;
00450       if (!t.unpack(curr._timestamp)) {
00451         show_message(istring("failed to interpret timestamp for ")
00452             + curr._target, ERROR_TITLE);
00453         return 97;
00454       }
00455       utimbuf held_time;
00456       held_time.actime = t.raw();
00457       held_time.modtime = t.raw();
00458       // put the timestamp on the file.
00459       utime(curr._target.s(), &held_time);
00460     }
00461 
00462     // now that we're pretty sure the file exists, we can run it if needed.
00463     if ( (curr._flags & TARGET_EXECUTE) && keyword_good) {    
00464       // change the mode on the target file so we can execute it.
00465       chmod(curr._target.s(), 0766);
00466       istring prev_dir = portable::current_directory();
00467 
00468       BASE_LOG(istring("launching ") + curr._target);
00469       if (!!curr._parms)
00470         BASE_LOG(istring("  with parameters: ") + curr._parms);
00471       BASE_LOG(istring('-', 76));
00472 
00473       u_int kid;
00474       u_int retval = portable::launch_process(curr._target, curr._parms,
00475           portable::AWAIT_APP_EXIT | portable::HIDE_APP_WINDOW, kid);
00476       if (retval != 0) {
00477         if (! (curr._flags & IGNORE_ERRORS) ) {
00478           if (curr._flags & QUIET_FAILURE) {
00479             // no message box for this, but still log it.
00480             LOG(istring("failed to launch process, targ=")
00481                 + curr._target + " with parms " + curr._parms
00482                 + isprintf(" error=%d", retval));
00483           } else {
00484             show_message(istring("failed to launch process, targ=")
00485                 + curr._target + " with parms " + curr._parms
00486                 + isprintf(" error=%d", retval), ERROR_TITLE);
00487           }
00488           return retval;  // pass along same exit value we were told.
00489         } else {
00490           LOG(istring("ignoring failure to launch process, targ=")
00491               + curr._target + " with parms " + curr._parms
00492               + isprintf(" error=%d", retval));
00493         }
00494       }
00495 
00496       chdir(prev_dir.s());  // reset directory pointer, just in case.
00497 
00498       BASE_LOG(istring('-', 76));
00499     }
00500 
00501   }
00502 
00503 #ifdef __WIN32__
00504   whack_simplistic_window(f_window);
00505 #endif
00506 
00507   return 0;
00508 }
00509 
00511 
00512 HOOPLE_MAIN(unpacker_stub, )
00513 
00514 #ifdef __BUILD_STATIC_APPLICATION__
00515   // static dependencies found by buildor_gen_deps.sh:
00516   #include <basis/array.cpp>
00517   #include <basis/byte_array.cpp>
00518   #include <basis/callstack_tracker.cpp>
00519   #include <basis/chaos.cpp>
00520   #include <basis/convert_utf.cpp>
00521   #include <basis/definitions.cpp>
00522   #include <basis/earth_time.cpp>
00523   #include <basis/guards.cpp>
00524   #include <basis/istring.cpp>
00525   #include <basis/log_base.cpp>
00526   #include <basis/memory_checker.cpp>
00527   #include <basis/mutex.cpp>
00528   #include <basis/object_base.cpp>
00529   #include <basis/outcome.cpp>
00530   #include <basis/packable.cpp>
00531   #include <basis/portable.cpp>
00532   #include <basis/sequence.cpp>
00533   #include <basis/set.cpp>
00534   #include <basis/utility.cpp>
00535   #include <basis/version_record.cpp>
00536   #include <data_struct/amorph.cpp>
00537   #include <data_struct/bit_vector.cpp>
00538   #include <data_struct/byte_hasher.cpp>
00539   #include <data_struct/configurator.cpp>
00540   #include <data_struct/hash_table.cpp>
00541   #include <data_struct/pointer_hash.cpp>
00542   #include <data_struct/stack.cpp>
00543   #include <data_struct/static_memory_gremlin.cpp>
00544   #include <data_struct/string_hash.cpp>
00545   #include <data_struct/string_hasher.cpp>
00546   #include <data_struct/string_table.cpp>
00547   #include <data_struct/symbol_table.cpp>
00548   #include <data_struct/table_configurator.cpp>
00549   #include <loggers/console_logger.cpp>
00550   #include <loggers/file_logger.cpp>
00551   #include <loggers/locked_logger.cpp>
00552   #include <loggers/null_logger.cpp>
00553   #include <loggers/program_wide_logger.cpp>
00554   #include <opsystem/application_base.cpp>
00555   #include <opsystem/application_shell.cpp>
00556   #include <opsystem/byte_filer.cpp>
00557   #include <opsystem/command_line.cpp>
00558   #include <opsystem/critical_events.cpp>
00559   #include <opsystem/directory.cpp>
00560   #include <opsystem/file_info.cpp>
00561   #include <opsystem/filename.cpp>
00562   #include <opsystem/filename_list.cpp>
00563   #include <opsystem/filetime.cpp>
00564   #include <opsystem/heavy_file_ops.cpp>
00565   #include <opsystem/huge_file.cpp>
00566   #include <opsystem/ini_config.cpp>
00567   #include <opsystem/ini_parser.cpp>
00568   #include <opsystem/path_configuration.cpp>
00569   #include <opsystem/rendezvous.cpp>
00570   #include <textual/byte_format.cpp>
00571   #include <textual/parser_bits.cpp>
00572   #include <textual/string_manipulation.cpp>
00573   #include <textual/tokenizer.cpp>
00574 #endif // __BUILD_STATIC_APPLICATION__
00575 

Generated on Fri Nov 21 04:28:59 2008 for HOOPLE Libraries by  doxygen 1.5.1