00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
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;
00028 bool append = false;
00029 bool update = false;
00030 bool tlist = false;
00031 bool extract = false;
00032 bool kill_cmd = false;
00033 bool verbose = false;
00034 bool compress_flag = false;
00035 int default_compression_level = 6;
00036 Filename multifile_name;
00037 bool got_multifile_name = false;
00038 bool to_stdout = false;
00039 bool encryption_flag = false;
00040 string password;
00041 bool got_password = false;
00042 string header_prefix;
00043 bool got_header_prefix = false;
00044 Filename chdir_to;
00045 bool got_chdir_to = false;
00046 size_t scale_factor = 0;
00047 pset<string> dont_compress;
00048 pset<string> text_ext;
00049 vector_string sign_params;
00050
00051
00052 string dont_compress_str = "jpg,png,mp3,ogg";
00053
00054
00055 string text_ext_str = "txt";
00056
00057 bool got_record_timestamp_flag = false;
00058 bool record_timestamp_flag = true;
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068
00069
00070
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
00083
00084
00085
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
00265
00266 if (params.empty()) {
00267
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
00284
00285
00286 string ext = subfile_name.get_extension();
00287 if (text_ext.find(ext) != text_ext.end()) {
00288
00289 return true;
00290 }
00291
00292 return false;
00293 }
00294
00295 int
00296 get_compression_level(const Filename &subfile_name) {
00297
00298 if (!compress_flag) {
00299
00300 return 0;
00301 }
00302
00303 string ext = subfile_name.get_extension();
00304 if (dont_compress.find(ext) != dont_compress.end()) {
00305
00306
00307 return 0;
00308 }
00309
00310
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
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
00459
00460
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
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
00557
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
00606 return "";
00607 }
00608
00609 if (timestamp == 0) {
00610
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
00619
00620 strftime(buffer, buffer_size, "%b %d %Y", tm_p);
00621 } else {
00622
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
00759
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
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
00926 tokenize_extensions(dont_compress_str, dont_compress);
00927
00928
00929 tokenize_extensions(text_ext_str, text_ext);
00930
00931
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 {
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 }