Panda3D
|
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 ¶ms) { 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 ¶ms) { 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 ¶ms) { 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 ¶ms) { 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 ¶m = (*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(×tamp); 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 ¶ms) { 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 }