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