Panda3D

multify.cxx

00001 // Filename: multify.cxx
00002 // Created by:  
00003 //
00004 ////////////////////////////////////////////////////////////////////
00005 //
00006 // PANDA 3D SOFTWARE
00007 // Copyright (c) Carnegie Mellon University.  All rights reserved.
00008 //
00009 // All use of this software is subject to the terms of the revised BSD
00010 // license.  You should have received a copy of this license along
00011 // with this source code in a file named "LICENSE."
00012 //
00013 ////////////////////////////////////////////////////////////////////
00014 
00015 #include "pandabase.h"
00016 #ifndef HAVE_GETOPT
00017   #include "gnu_getopt.h"
00018 #else
00019   #include <getopt.h>
00020 #endif
00021 #include "multifile.h"
00022 #include "pointerTo.h"
00023 #include "filename.h"
00024 #include "pset.h"
00025 #include "vector_string.h"
00026 #include <stdio.h>
00027 #include <time.h>
00028 
00029 
00030 bool create = false;           // -c
00031 bool append = false;           // -r
00032 bool update = false;           // -u
00033 bool tlist = false;            // -t
00034 bool extract = false;          // -x
00035 bool kill_cmd = false;         // -k
00036 bool verbose = false;          // -v
00037 bool compress_flag = false;    // -z
00038 int default_compression_level = 6;
00039 Filename multifile_name;       // -f
00040 bool got_multifile_name = false;
00041 bool to_stdout = false;        // -O
00042 bool encryption_flag = false;  // -e
00043 string password;               // -p
00044 bool got_password = false;
00045 string header_prefix;          // -P
00046 bool got_header_prefix = false;
00047 Filename chdir_to;             // -C
00048 bool got_chdir_to = false;
00049 size_t scale_factor = 0;       // -F
00050 pset<string> dont_compress;    // -Z
00051 vector_string sign_params;     // -S
00052 
00053 // Default extensions not to compress.  May be overridden with -Z.
00054 string dont_compress_str = "jpg,png,mp3,ogg";
00055 
00056 bool got_record_timestamp_flag = false;
00057 bool record_timestamp_flag = true;
00058 
00059 ////////////////////////////////////////////////////////////////////
00060 //     Function: string_to_int
00061 //  Description: A string-interface wrapper around the C library
00062 //               strtol().  This parses the ASCII representation of an
00063 //               integer, and then sets tail to everything that
00064 //               follows the first valid integer read.  If, on exit,
00065 //               str == tail, there was no valid integer in the
00066 //               source string; if !tail.empty(), there was garbage
00067 //               after the integer.
00068 //
00069 //               It is legal if str and tail refer to the same string.
00070 ////////////////////////////////////////////////////////////////////
00071 static int
00072 string_to_int(const string &str, string &tail) {
00073   const char *nptr = str.c_str();
00074   char *endptr;
00075   int result = strtol(nptr, &endptr, 10);
00076   tail = endptr;
00077   return result;
00078 }
00079 
00080 ////////////////////////////////////////////////////////////////////
00081 //     Function: string_to_int
00082 //  Description: Another flavor of string_to_int(), this one returns
00083 //               true if the string is a perfectly valid integer (and
00084 //               sets result to that value), or false otherwise.
00085 ////////////////////////////////////////////////////////////////////
00086 static bool
00087 string_to_int(const string &str, int &result) {
00088   string tail;
00089   result = string_to_int(str, tail);
00090   return tail.empty();
00091 }
00092 
00093 void 
00094 usage() {
00095   cerr <<
00096     "Usage: multify -[c|r|u|t|x] -f <multifile_name> [options] <subfile_name> ...\n";
00097 }
00098 
00099 void 
00100 help() {
00101   usage();
00102   cerr << "\n"
00103     "multify is used to store and extract files from a Panda Multifile.\n"
00104     "This is similar to a tar or zip file in that it is an archive file that\n"
00105     "contains a number of subfiles that may later be extracted.\n\n"
00106 
00107     "Panda's VirtualFileSystem is capable of mounting Multifiles for direct\n"
00108     "access to the subfiles contained within without having to extract them\n"
00109     "out to independent files first.\n\n"
00110 
00111     "The command-line options for multify are designed to be similar to those\n"
00112     "for tar, the traditional Unix archiver utility.\n\n"
00113 
00114     "Options:\n\n"
00115     
00116     "  You must specify exactly one of the following command switches:\n\n"
00117 
00118     "  -c\n"
00119     "      Create a new Multifile archive.  Subfiles named on the command line\n"
00120     "      will be added to the new Multifile.  If the Multifile already exists,\n"
00121     "      it is first removed.\n\n"
00122 
00123     "  -r\n"
00124     "      Rewrite an existing Multifile archive.  Subfiles named on the command\n"
00125     "      line will be added to the Multifile or will replace subfiles within\n"
00126     "      the Multifile with the same name.  The Multifile will be repacked\n"
00127     "      after completion, even if no Subfiles were added.\n\n"
00128 
00129     "  -u\n"
00130     "      Update an existing Multifile archive.  This is similar to -r, except\n"
00131     "      that files are compared byte-for-byte with their corresponding files\n"
00132     "      in the archive first.  If they have not changed, the multifile is not\n"
00133     "      modified (other than to repack it if necessary).\n\n"
00134 
00135     "  -t\n"
00136     "      List the contents of an existing Multifile.  With -v, this shows\n"
00137     "      the size of each Subfile and its compression ratio, if compressed.\n\n"
00138 
00139     "  -x\n"
00140     "      Extract the contents of an existing Multifile.  The Subfiles named on\n"
00141     "      the command line, or all Subfiles if nothing is named, are extracted\n"
00142     "      into the current directory or into whichever directory is specified\n"
00143     "      with -C.\n\n"
00144 
00145     "  -k\n"
00146     "      Delete (kill) the named Subfiles from the Multifile.  The Multifile\n"
00147     "      will be repacked after completion.\n\n"
00148 
00149     "\n"
00150     "  You must always specify the following switch:\n\n"
00151 
00152     "  -f <multifile_name>\n"
00153     "      Names the Multifile that will be operated on.\n\n\n"
00154 
00155     "  The remaining switches are optional:\n\n"
00156 
00157     "  -v\n"
00158     "      Run verbosely.  In -c, -r, or -x mode, list each file as it is\n"
00159     "      written or extracted.  In -t mode, list more information about each\n"
00160     "      file.\n\n"
00161 
00162     "  -z\n"
00163     "      Compress subfiles as they are written to the Multifile.  Unlike tar\n"
00164     "      (but like zip), subfiles are compressed individually, instead of the\n"
00165     "      entire archive being compressed as one stream.  It is not necessary\n"
00166     "      to specify -z when extracting compressed subfiles; they will always be\n"
00167     "      decompressed automatically.  Also see -Z, which restricts which\n"
00168     "      subfiles will be compressed based on the filename extension.\n\n"
00169 
00170     "  -e\n"
00171     "      Encrypt subfiles as they are written to the Multifile using the password\n"
00172     "      specified with -p, below.  Subfiles are encrypted individually, rather\n"
00173     "      than encrypting the entire multifile, and different subfiles may be\n"
00174     "      encrypted using different passwords (although this requires running\n"
00175     "      multify multiple times).  It is not possible to encrypt the multifile's\n"
00176     "      table of contents using this interface, but see the pencrypt program to\n"
00177     "      encrypt the entire multifile after it has been generated.\n\n"
00178 
00179 
00180     "  -p \"password\"\n"
00181     "      Specifies the password to encrypt or decrypt subfiles.  If this is not\n"
00182     "      specified, and passwords are required, the user will be prompted from\n"
00183     "      standard input.\n\n"
00184 
00185     "  -P \"prefix\"\n"
00186     "      Specifies a header_prefix to write to the beginning of the multifile.\n"
00187     "      This is primarily useful for creating a multifile that can be invoked\n"
00188     "      directly as a program from the shell on Unix-like environments,\n"
00189     "      for instance, p3d files.  The header_prefix must begin with a hash\n"
00190     "      mark and end with a newline; this will be enforced if it is not\n"
00191     "      already so.  This only has effect in conjunction with with -c, -u,\n"
00192     "      or -k.\n\n"
00193 
00194     "  -F <scale_factor>\n"
00195     "      Specify a Multifile scale factor.  This is only necessary to support\n"
00196     "      Multifiles that will exceed 4GB in size.  The default scale factor is\n"
00197     "      1, which should be sufficient for almost any application, but the total\n"
00198     "      size of the Multifile will be limited to 4GB * scale_factor.  The size\n"
00199     "      of individual subfiles may not exceed 4GB in any case.\n\n"
00200 
00201     "  -C <extract_dir>\n"
00202 
00203     "      With -x, change to the named directory before extracting files;\n"
00204     "      that is, extract subfiles into the named directory.\n\n"
00205 
00206     "  -O\n"
00207     "      With -x, extract subfiles to standard output instead of to disk.\n\n"
00208     "  -Z <extension_list>\n"
00209     "      Specify a comma-separated list of filename extensions that represent\n"
00210     "      files that are not to be compressed.  The default if this is omitted is\n"
00211     "      \"" << dont_compress_str << "\".  Specify -Z \"\" (be sure to include the space) to allow\n"
00212     "      all files to be compressed.\n\n"
00213 
00214     "  -T <flag>\n"
00215     "      Enable or disable the recording of file timestamps within the multifile.\n"
00216     "      If <flag> is 1, timestamps will be recorded within the multifile for\n"
00217     "      each subfile added; this is the default behavior.  If <flag> is 0,\n"
00218     "      timestamps will not be recorded, which will make it easier to do a\n"
00219     "      bitwise comparison between multifiles to determine whether their\n"
00220     "      contents are equivalent.\n\n"
00221 
00222     "  -1 .. -9\n"
00223     "      Specify the compression level when -z is in effect.  Larger numbers\n"
00224     "      generate slightly smaller files, but compression takes longer.  The\n"
00225     "      default is -" << default_compression_level << ".\n\n"
00226 
00227     "  -S file.crt[,chain.crt[,file.key[,\"password\"]]]\n"
00228     "      Sign the multifile.  The signing certificate should be in PEM form in\n"
00229     "      file.crt, with its private key in PEM form in file.key.  If the key\n"
00230     "      is encrypted on-disk, specify the decryption password as the third\n"
00231     "      option.  If a certificate chain is required, chain.crt should also\n"
00232     "      be specified; note that the separating commas should be supplied\n"
00233     "      even if this optional filename is omitted.\n"
00234     "      You may also provide a single composite file that contains the\n"
00235     "      certificate, chain, and key in the same file.\n"
00236     "      PEM form is the form accepted by the Apache web server.  The\n"
00237     "      signature is written to the multifile to prove it is unchanged; any\n"
00238     "      subsequent change to the multifile will invalidate the signature.\n"
00239     "      This parameter may be repeated to sign the multifile with additional\n"
00240     "      certificates.\n\n";
00241 }
00242 
00243 const string &
00244 get_password() {
00245   if (!got_password) {
00246     cerr << "Enter password: ";
00247     getline(cin, password);
00248     got_password = true;
00249   }
00250 
00251   return password;
00252 }
00253 
00254 
00255 bool
00256 is_named(const string &subfile_name, const vector_string &params) {
00257   // Returns true if the indicated subfile appears on the list of
00258   // files named on the command line.
00259   if (params.empty()) {
00260     // No named files; everything is listed.
00261     return true;
00262   }
00263 
00264   vector_string::const_iterator pi;
00265   for (pi = params.begin(); pi != params.end(); ++pi) {
00266     if (subfile_name == (*pi)) {
00267       return true;
00268     }
00269   }
00270 
00271   return false;
00272 }
00273 
00274 int
00275 get_compression_level(const Filename &subfile_name) {
00276   // Returns the appropriate compression level for the named file.
00277   if (!compress_flag) {
00278     // Don't compress anything.
00279     return 0;
00280   }
00281 
00282   string ext = subfile_name.get_extension();
00283   if (dont_compress.find(ext) != dont_compress.end()) {
00284     // This extension is listed on the -Z parameter list; don't
00285     // compress it.
00286     return 0;
00287   }
00288 
00289   // Go ahead and compress this file.
00290   return default_compression_level;
00291 }
00292 
00293 bool
00294 do_add_files(Multifile *multifile, const pvector<Filename> &filenames);
00295 
00296 bool
00297 do_add_directory(Multifile *multifile, const Filename &directory_name) {
00298   vector_string files;
00299   if (!directory_name.scan_directory(files)) {
00300     cerr << "Unable to scan directory " << directory_name << "\n";
00301     return false;
00302   }
00303 
00304   pvector<Filename> filenames;
00305   filenames.reserve(files.size());
00306   vector_string::const_iterator fi;
00307   for (fi = files.begin(); fi != files.end(); ++fi) {
00308     Filename subfile_name(directory_name, (*fi));
00309     filenames.push_back(subfile_name);
00310   }
00311 
00312   return do_add_files(multifile, filenames);
00313 }
00314 
00315 bool
00316 do_add_files(Multifile *multifile, const pvector<Filename> &filenames) {
00317   bool okflag = true;
00318   pvector<Filename>::const_iterator fi;
00319   for (fi = filenames.begin(); fi != filenames.end(); ++fi) {
00320     const Filename &subfile_name = (*fi);
00321 
00322     if (subfile_name.is_directory()) {
00323       if (!do_add_directory(multifile, subfile_name)) {
00324         okflag = false;
00325       }
00326 
00327     } else if (!subfile_name.exists()) {
00328       cerr << "Not found: " << subfile_name << "\n";
00329       okflag = false;
00330 
00331     } else {
00332       string new_subfile_name;
00333       if (update) {
00334         new_subfile_name = multifile->update_subfile
00335           (subfile_name, subfile_name, get_compression_level(subfile_name));
00336       } else {
00337         new_subfile_name = multifile->add_subfile
00338           (subfile_name, subfile_name, get_compression_level(subfile_name));
00339       }
00340       if (new_subfile_name.empty()) {
00341         cerr << "Unable to add " << subfile_name << ".\n";
00342         okflag = false;
00343       } else {
00344         if (verbose) {
00345           cout << new_subfile_name << "\n";
00346         }
00347       }
00348     }
00349   }
00350   return okflag;
00351 }
00352 
00353 bool
00354 add_files(const vector_string &params) {
00355   PT(Multifile) multifile = new Multifile;
00356   if (append || update) {
00357     if (!multifile->open_read_write(multifile_name)) {
00358       cerr << "Unable to open " << multifile_name << " for updating.\n";
00359       return false;
00360     }
00361   } else {
00362     if (!multifile->open_write(multifile_name)) {
00363       cerr << "Unable to open " << multifile_name << " for writing.\n";
00364       return false;
00365     }
00366   }
00367   
00368   if (got_record_timestamp_flag) {
00369     multifile->set_record_timestamp(record_timestamp_flag);
00370   }
00371 
00372   if (encryption_flag) {
00373     multifile->set_encryption_flag(true);
00374     multifile->set_encryption_password(get_password());
00375   }
00376 
00377   if (got_header_prefix) {
00378     multifile->set_header_prefix(header_prefix);
00379   }
00380 
00381   if (scale_factor != 0 && scale_factor != multifile->get_scale_factor()) {
00382     cerr << "Setting scale factor to " << scale_factor << "\n";
00383     multifile->set_scale_factor(scale_factor);
00384   }
00385 
00386   pvector<Filename> filenames;
00387   filenames.reserve(params.size());
00388   vector_string::const_iterator si;
00389   for (si = params.begin(); si != params.end(); ++si) {
00390     Filename subfile_name = Filename::from_os_specific(*si);
00391     filenames.push_back(subfile_name);
00392   }
00393 
00394   bool okflag = do_add_files(multifile, filenames);
00395 
00396   bool needs_repack = multifile->needs_repack();
00397   if (append) {
00398     // If we specified -r mode, we always repack.
00399     needs_repack = true;
00400   }
00401 
00402   if (needs_repack) {
00403     if (!multifile->repack()) {
00404       cerr << "Failed to write " << multifile_name << ".\n";
00405       okflag = false;
00406     }
00407   } else {
00408     if (!multifile->flush()) {
00409       cerr << "Failed to write " << multifile_name << ".\n";
00410       okflag = false;
00411     }
00412   }
00413 
00414   return okflag;
00415 }
00416 
00417 bool
00418 extract_files(const vector_string &params) {
00419   if (!multifile_name.exists()) {
00420     cerr << multifile_name << " not found.\n";
00421     return false;
00422   }
00423   PT(Multifile) multifile = new Multifile;
00424   if (!multifile->open_read(multifile_name)) {
00425     cerr << "Unable to open " << multifile_name << " for reading.\n";
00426     return false;
00427   }
00428 
00429   int num_subfiles = multifile->get_num_subfiles();
00430 
00431   // First, check to see whether any of the named subfiles have been
00432   // encrypted.  If any have, we may need to prompt the user to enter
00433   // a password before we can extract them.
00434   int i;
00435   bool any_encrypted = false;
00436   for (i = 0; i < num_subfiles && !any_encrypted; i++) {
00437     string subfile_name = multifile->get_subfile_name(i);
00438     if (is_named(subfile_name, params)) {
00439       if (multifile->is_subfile_encrypted(i)) {
00440         any_encrypted = true;
00441       }
00442     }
00443   }
00444 
00445   if (any_encrypted) {
00446     multifile->set_encryption_password(get_password());
00447   }
00448 
00449   // Now walk back through the list and this time do the extraction.
00450   for (i = 0; i < num_subfiles; i++) {
00451     string subfile_name = multifile->get_subfile_name(i);
00452     if (is_named(subfile_name, params)) {
00453       Filename filename = subfile_name;
00454       if (got_chdir_to) {
00455         filename = Filename(chdir_to, subfile_name);
00456       }
00457       if (to_stdout) {
00458         if (verbose) {
00459           cerr << filename << "\n";
00460         }
00461         multifile->extract_subfile_to(i, cout);
00462       } else {
00463         if (verbose) {
00464           cout << filename << "\n";
00465         }
00466         multifile->extract_subfile(i, filename);
00467       }
00468     }
00469   }
00470 
00471   return true;
00472 }
00473 
00474 bool
00475 kill_files(const vector_string &params) {
00476   if (!multifile_name.exists()) {
00477     cerr << multifile_name << " not found.\n";
00478     return false;
00479   }
00480   PT(Multifile) multifile = new Multifile;
00481   if (!multifile->open_read_write(multifile_name)) {
00482     cerr << "Unable to open " << multifile_name << " for read/write.\n";
00483     return false;
00484   }
00485 
00486   if (got_header_prefix) {
00487     multifile->set_header_prefix(header_prefix);
00488   }
00489 
00490   int i = 0;
00491   while (i < multifile->get_num_subfiles()) {
00492     string subfile_name = multifile->get_subfile_name(i);
00493     if (is_named(subfile_name, params)) {
00494       Filename filename = subfile_name;
00495 
00496       if (verbose) {
00497         cout << filename << "\n";
00498       }
00499       multifile->remove_subfile(i);
00500     } else {
00501       ++i;
00502     }
00503   }
00504 
00505   bool okflag = true;
00506 
00507   if (multifile->needs_repack()) {
00508     if (!multifile->repack()) {
00509       cerr << "Failed to write " << multifile_name << ".\n";
00510       okflag = false;
00511     }
00512   } else {
00513     if (!multifile->flush()) {
00514       cerr << "Failed to write " << multifile_name << ".\n";
00515       okflag = false;
00516     }
00517   }
00518 
00519   return okflag;
00520 }
00521 
00522 bool
00523 sign_multifile() {
00524 #ifndef HAVE_OPENSSL
00525   cerr << "Cannot sign multifiles without OpenSSL compiled in.\n";
00526   return false;
00527 
00528 #else  // HAVE_OPENSSL
00529   // Re-open the Multifile, and sign it with the indicated certificate
00530   // and key files.
00531   PT(Multifile) multifile = new Multifile;
00532   if (!multifile->open_read_write(multifile_name)) {
00533     cerr << "Unable to re-open " << multifile_name << " for signing.\n";
00534     return false;
00535   }
00536 
00537   vector_string::iterator si;
00538   for (si = sign_params.begin(); si != sign_params.end(); ++si) {
00539     const string &param = (*si);
00540     Filename certificate, chain, pkey;
00541     string password;
00542 
00543     size_t comma1 = param.find(',');
00544     if (comma1 == string::npos) {
00545       certificate = Filename::from_os_specific(param);
00546     } else {
00547       certificate = Filename::from_os_specific(param.substr(0, comma1));
00548       size_t comma2 = param.find(',', comma1 + 1);
00549       if (comma2 == string::npos) {
00550         chain = Filename::from_os_specific(param.substr(comma1 + 1));
00551       } else {
00552         chain = Filename::from_os_specific(param.substr(comma1 + 1, comma2 - comma1 - 1));
00553         size_t comma3 = param.find(',', comma2 + 1);
00554         if (comma3 == string::npos) {
00555           pkey = Filename::from_os_specific(param.substr(comma2 + 1));
00556         } else {
00557           pkey = Filename::from_os_specific(param.substr(comma2 + 1, comma3 - comma2 - 1));
00558           password = param.substr(comma3 + 1);
00559         }
00560       }
00561     }
00562 
00563     if (!multifile->add_signature(certificate, chain, pkey, password)) {
00564       return false;
00565     }
00566   }    
00567 
00568   return true;
00569 #endif  // HAVE_OPENSSL
00570 }
00571 
00572 const char *
00573 format_timestamp(bool record_timestamp, time_t timestamp) {
00574   static const size_t buffer_size = 512;
00575   static char buffer[buffer_size];
00576 
00577   if (!record_timestamp) {
00578     // No timestamps.
00579     return "";
00580   }
00581   
00582   if (timestamp == 0) {
00583     // A zero timestamp is a special case.
00584     return "  (no date) ";
00585   }
00586 
00587   time_t now = time(NULL);
00588   struct tm *tm_p = localtime(&timestamp);
00589 
00590   if (timestamp > now || (now - timestamp > 86400 * 365)) {
00591     // A timestamp in the future, or more than a year in the past,
00592     // gets a year appended.
00593     strftime(buffer, buffer_size, "%b %d  %Y", tm_p);
00594   } else {
00595     // Otherwise, within the past year, show the date and time.
00596     strftime(buffer, buffer_size, "%b %d %H:%M", tm_p);
00597   }
00598 
00599   return buffer;
00600 }
00601 
00602 bool
00603 list_files(const vector_string &params) {
00604   if (!multifile_name.exists()) {
00605     cerr << multifile_name << " not found.\n";
00606     return false;
00607   }
00608   PT(Multifile) multifile = new Multifile;
00609   if (!multifile->open_read(multifile_name)) {
00610     cerr << "Unable to open " << multifile_name << " for reading.\n";
00611     return false;
00612   }
00613 
00614   int num_subfiles = multifile->get_num_subfiles();
00615   
00616   int i;
00617   if (verbose) {
00618     cout << num_subfiles << " subfiles:\n" << flush;
00619     for (i = 0; i < num_subfiles; i++) {
00620       string subfile_name = multifile->get_subfile_name(i);
00621       if (is_named(subfile_name, params)) {
00622         char encrypted_symbol = ' ';
00623         if (multifile->is_subfile_encrypted(i)) {
00624           encrypted_symbol = 'e';
00625         }
00626         if (multifile->is_subfile_compressed(i)) {
00627           size_t orig_length = multifile->get_subfile_length(i);
00628           size_t internal_length = multifile->get_subfile_internal_length(i);
00629           double ratio = 1.0;
00630           if (orig_length != 0) {
00631             ratio = (double)internal_length / (double)orig_length;
00632           }
00633           if (ratio > 1.0) {
00634             printf("%12d worse %c %s %s\n",
00635                    (int)multifile->get_subfile_length(i),
00636                    encrypted_symbol, 
00637                    format_timestamp(multifile->get_record_timestamp(),
00638                                     multifile->get_subfile_timestamp(i)),
00639                    subfile_name.c_str());
00640           } else {
00641             printf("%12d  %3.0f%% %c %s %s\n",
00642                    (int)multifile->get_subfile_length(i),
00643                    100.0 - ratio * 100.0, 
00644                    encrypted_symbol, 
00645                    format_timestamp(multifile->get_record_timestamp(),
00646                                     multifile->get_subfile_timestamp(i)),
00647                    subfile_name.c_str());
00648           }
00649         } else {
00650           printf("%12d       %c %s %s\n", 
00651                  (int)multifile->get_subfile_length(i),
00652                  encrypted_symbol, 
00653                  format_timestamp(multifile->get_record_timestamp(),
00654                                   multifile->get_subfile_timestamp(i)),
00655                  subfile_name.c_str());
00656         }
00657       }
00658     }
00659     fflush(stdout);
00660 
00661     if (multifile->get_record_timestamp()) {
00662       cout << "Last modification " 
00663            << format_timestamp(true, multifile->get_timestamp()) << "\n";
00664     }
00665 
00666     if (multifile->get_scale_factor() != 1) {
00667       cout << "Scale factor is " << multifile->get_scale_factor() << "\n";
00668     }
00669     if (multifile->needs_repack()) {
00670       cout << "Multifile needs to be repacked.\n";
00671     }
00672   } else {
00673     for (i = 0; i < num_subfiles; i++) {
00674       string subfile_name = multifile->get_subfile_name(i);
00675       if (is_named(subfile_name, params)) {
00676         cout << subfile_name << "\n";
00677       }
00678     }
00679   }
00680 
00681 #ifdef HAVE_OPENSSL
00682   int num_signatures = multifile->get_num_signatures();
00683   if (num_signatures != 0) {
00684     cout << "\n";
00685     for (i = 0; i < num_signatures; ++i) {
00686       cout << "Signed by " << multifile->get_signature_friendly_name(i);
00687       int verify_result = multifile->validate_signature_certificate(i);
00688       if (verify_result == 0) {
00689         cout << " (certificate validated)\n";
00690       } else {
00691         cout << " (certificate unknown, reason " << verify_result << ")\n";
00692       }
00693       if (verbose) {
00694         multifile->write_signature_certificate(i, cout);
00695         cout << "\n";
00696       }
00697     }
00698   }
00699 #endif  // HAVE_OPENSSL
00700 
00701   return true;
00702 }
00703 
00704 void
00705 tokenize_extensions(const string &str, pset<string> &extensions) {
00706   size_t p = 0;
00707   while (p < str.length()) {
00708     size_t q = str.find_first_of(",", p);
00709     if (q == string::npos) {
00710       extensions.insert(str.substr(p));
00711       return;
00712     }
00713     extensions.insert(str.substr(p, q - p));
00714     p = q + 1;
00715   }
00716   extensions.insert(string());
00717 }
00718 
00719 int
00720 main(int argc, char *argv[]) {
00721   if (argc < 2) {
00722     usage();
00723     return 1;
00724   }
00725 
00726   // To emulate tar, we assume an implicit hyphen in front of the
00727   // first argument if there is not one already.
00728   if (argc >= 2) {
00729     if (*argv[1] != '-' && *argv[1] != '\0') {
00730       char *new_arg = (char *)PANDA_MALLOC_ARRAY(strlen(argv[1]) + 2);
00731       new_arg[0] = '-';
00732       strcpy(new_arg + 1, argv[1]);
00733       argv[1] = new_arg;
00734     }
00735   }
00736 
00737   extern char *optarg;
00738   extern int optind;
00739   static const char *optflags = "crutxkvz123456789Z:T:S:f:OC:ep:P:F:h";
00740   int flag = getopt(argc, argv, optflags);
00741   Filename rel_path;
00742   while (flag != EOF) {
00743     switch (flag) {
00744     case 'c':
00745       create = true;
00746       break;
00747     case 'r':
00748       append = true;
00749       break;
00750     case 'u':
00751       update = true;
00752       break;
00753     case 't':
00754       tlist = true;
00755       break;
00756     case 'x':
00757       extract = true;
00758       break;
00759     case 'k':
00760       kill_cmd = true;
00761       break;
00762     case 'v':
00763       verbose = true;
00764       break;
00765     case 'z':
00766       compress_flag = true;
00767       break;
00768     case '1':
00769       default_compression_level = 1;
00770       compress_flag = true;
00771       break;
00772     case '2':
00773       default_compression_level = 2;
00774       compress_flag = true;
00775       break;
00776     case '3':
00777       default_compression_level = 3;
00778       compress_flag = true;
00779       break;
00780     case '4':
00781       default_compression_level = 4;
00782       compress_flag = true;
00783       break;
00784     case '5':
00785       default_compression_level = 5;
00786       compress_flag = true;
00787       break;
00788     case '6':
00789       default_compression_level = 6;
00790       compress_flag = true;
00791       break;
00792     case '7':
00793       default_compression_level = 7;
00794       compress_flag = true;
00795       break;
00796     case '8':
00797       default_compression_level = 8;
00798       compress_flag = true;
00799       break;
00800     case '9':
00801       default_compression_level = 9;
00802       compress_flag = true;
00803       break;
00804     case 'Z':
00805       dont_compress_str = optarg;
00806       break;
00807     case 'S':
00808       sign_params.push_back(optarg);
00809       break;
00810     case 'T':
00811       {
00812         int flag;
00813         if (!string_to_int(optarg, flag) ||
00814             (flag != 0 && flag != 1)) {
00815           cerr << "Invalid timestamp flag: " << optarg << "\n";
00816           usage();
00817           return 1;
00818         }
00819         record_timestamp_flag = (flag != 0);
00820         got_record_timestamp_flag = true;
00821       }
00822       break;
00823     case 'f':
00824       multifile_name = Filename::from_os_specific(optarg);
00825       got_multifile_name = true;
00826       break;
00827     case 'C':
00828       chdir_to = Filename::from_os_specific(optarg);
00829       got_chdir_to = true;
00830       break;
00831     case 'O':
00832       to_stdout = true;
00833       break;
00834     case 'e':
00835       encryption_flag = true;
00836       break;
00837     case 'p':
00838       password = optarg;
00839       got_password = true;
00840       break;
00841     case 'P':
00842       header_prefix = optarg;
00843       got_header_prefix = true;
00844       break;
00845     case 'F':
00846       {
00847         char *endptr;
00848         scale_factor = strtol(optarg, &endptr, 10);
00849         if (*endptr != '\0') {
00850           cerr << "Invalid integer: " << optarg << "\n";
00851           usage();
00852           return 1;
00853         }
00854         if (scale_factor == 0) {
00855           cerr << "Scale factor must be nonzero.\n";
00856           usage();
00857           return 1;
00858         }
00859       }
00860       break;
00861 
00862     case 'h':
00863       help();
00864       return 1;
00865     case '?':
00866       usage();
00867       return 1;
00868     default:
00869       cerr << "Unhandled switch: " << flag << endl;
00870       break;
00871     }
00872     flag = getopt(argc, argv, optflags);
00873   }
00874   argc -= (optind - 1);
00875   argv += (optind - 1);
00876 
00877   // We should have exactly one of these options.
00878   if ((create?1:0) + (append?1:0) + (update?1:0) + (tlist?1:0) + (extract?1:0) + (kill_cmd?1:0) != 1) {
00879     cerr << "Exactly one of -c, -r, -u, -t, -x, -k must be specified.\n";
00880     usage();
00881     return 1;
00882   }
00883 
00884   if (!got_multifile_name) {
00885     cerr << "Multifile name not specified.\n";
00886     usage();
00887     return 1;
00888   }
00889 
00890   // Split out the extensions named by -Z into different words.
00891   tokenize_extensions(dont_compress_str, dont_compress);
00892 
00893   // Build a list of remaining parameters.
00894   vector_string params;
00895   params.reserve(argc - 1);
00896   for (int i = 1; i < argc; i++) {
00897     params.push_back(argv[i]);
00898   }
00899 
00900   bool okflag = true;
00901   if (create || append || update) {
00902     okflag = add_files(params);
00903   } else if (extract) {
00904     if (got_record_timestamp_flag) {
00905       cerr << "Warning: -T ignored on extract.\n";
00906     }
00907     okflag = extract_files(params);
00908   } else if (kill_cmd) {
00909     if (got_record_timestamp_flag) {
00910       cerr << "Warning: -T ignored on kill.\n";
00911     }
00912     okflag = kill_files(params);
00913   } else { // list
00914     if (got_record_timestamp_flag) {
00915       cerr << "Warning: -T ignored on list.\n";
00916     }
00917     okflag = list_files(params);
00918   }
00919 
00920   if (okflag && !sign_params.empty()) {
00921     sign_multifile();
00922   }
00923 
00924   if (okflag) {
00925     return 0;
00926   } else {
00927     return 1;
00928   }
00929 }
 All Classes Functions Variables Enumerations