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