Panda3D
 All Classes Functions Variables Enumerations
multify.cxx
1 // Filename: multify.cxx
2 // Created by:
3 //
4 ////////////////////////////////////////////////////////////////////
5 //
6 // PANDA 3D SOFTWARE
7 // Copyright (c) Carnegie Mellon University. All rights reserved.
8 //
9 // All use of this software is subject to the terms of the revised BSD
10 // license. You should have received a copy of this license along
11 // with this source code in a file named "LICENSE."
12 //
13 ////////////////////////////////////////////////////////////////////
14 
15 #include "pandabase.h"
16 #include "pystub.h"
17 #include "panda_getopt.h"
18 #include "preprocess_argv.h"
19 #include "multifile.h"
20 #include "pointerTo.h"
21 #include "filename.h"
22 #include "pset.h"
23 #include "vector_string.h"
24 #include <stdio.h>
25 #include <time.h>
26 
27 
28 bool create = false; // -c
29 bool append = false; // -r
30 bool update = false; // -u
31 bool tlist = false; // -t
32 bool extract = false; // -x
33 bool kill_cmd = false; // -k
34 bool verbose = false; // -v
35 bool compress_flag = false; // -z
36 int default_compression_level = 6;
37 Filename multifile_name; // -f
38 bool got_multifile_name = false;
39 bool to_stdout = false; // -O
40 bool encryption_flag = false; // -e
41 string password; // -p
42 bool got_password = false;
43 string header_prefix; // -P
44 bool got_header_prefix = false;
45 Filename chdir_to; // -C
46 bool got_chdir_to = false;
47 size_t scale_factor = 0; // -F
48 pset<string> dont_compress; // -Z
49 pset<string> text_ext; // -X
50 vector_string sign_params; // -S
51 
52 // Default extensions not to compress. May be overridden with -Z.
53 string dont_compress_str = "jpg,png,mp3,ogg";
54 
55 // Default text extensions. May be overridden with -X.
56 string text_ext_str = "txt";
57 
58 bool got_record_timestamp_flag = false;
59 bool record_timestamp_flag = true;
60 
61 ////////////////////////////////////////////////////////////////////
62 // Function: string_to_int
63 // Description: A string-interface wrapper around the C library
64 // strtol(). This parses the ASCII representation of an
65 // integer, and then sets tail to everything that
66 // follows the first valid integer read. If, on exit,
67 // str == tail, there was no valid integer in the
68 // source string; if !tail.empty(), there was garbage
69 // after the integer.
70 //
71 // It is legal if str and tail refer to the same string.
72 ////////////////////////////////////////////////////////////////////
73 static int
74 string_to_int(const string &str, string &tail) {
75  const char *nptr = str.c_str();
76  char *endptr;
77  int result = strtol(nptr, &endptr, 10);
78  tail = endptr;
79  return result;
80 }
81 
82 ////////////////////////////////////////////////////////////////////
83 // Function: string_to_int
84 // Description: Another flavor of string_to_int(), this one returns
85 // true if the string is a perfectly valid integer (and
86 // sets result to that value), or false otherwise.
87 ////////////////////////////////////////////////////////////////////
88 static bool
89 string_to_int(const string &str, int &result) {
90  string tail;
91  result = string_to_int(str, tail);
92  return tail.empty();
93 }
94 
95 void
96 usage() {
97  cerr <<
98  "Usage: multify -[c|r|u|t|x] -f <multifile_name> [options] <subfile_name> ...\n";
99 }
100 
101 void
102 help() {
103  usage();
104  cerr << "\n"
105  "multify is used to store and extract files from a Panda Multifile.\n"
106  "This is similar to a tar or zip file in that it is an archive file that\n"
107  "contains a number of subfiles that may later be extracted.\n\n"
108 
109  "Panda's VirtualFileSystem is capable of mounting Multifiles for direct\n"
110  "access to the subfiles contained within without having to extract them\n"
111  "out to independent files first.\n\n"
112 
113  "The command-line options for multify are designed to be similar to those\n"
114  "for tar, the traditional Unix archiver utility.\n\n"
115 
116  "Options:\n\n"
117 
118  " You must specify exactly one of the following command switches:\n\n"
119 
120  " -c\n"
121  " Create a new Multifile archive. Subfiles named on the command line\n"
122  " will be added to the new Multifile. If the Multifile already exists,\n"
123  " it is first removed.\n\n"
124 
125  " -r\n"
126  " Rewrite an existing Multifile archive. Subfiles named on the command\n"
127  " line will be added to the Multifile or will replace subfiles within\n"
128  " the Multifile with the same name. The Multifile will be repacked\n"
129  " after completion, even if no Subfiles were added.\n\n"
130 
131  " -u\n"
132  " Update an existing Multifile archive. This is similar to -r, except\n"
133  " that files are compared byte-for-byte with their corresponding files\n"
134  " in the archive first. If they have not changed, the multifile is not\n"
135  " modified (other than to repack it if necessary).\n\n"
136 
137  " -t\n"
138  " List the contents of an existing Multifile. With -v, this shows\n"
139  " the size of each Subfile and its compression ratio, if compressed.\n\n"
140 
141  " -x\n"
142  " Extract the contents of an existing Multifile. The Subfiles named on\n"
143  " the command line, or all Subfiles if nothing is named, are extracted\n"
144  " into the current directory or into whichever directory is specified\n"
145  " with -C.\n\n"
146 
147  " -k\n"
148  " Delete (kill) the named Subfiles from the Multifile. The Multifile\n"
149  " will be repacked after completion.\n\n"
150 
151  "\n"
152  " You must always specify the following switch:\n\n"
153 
154  " -f <multifile_name>\n"
155  " Names the Multifile that will be operated on.\n\n\n"
156 
157  " The remaining switches are optional:\n\n"
158 
159  " -v\n"
160  " Run verbosely. In -c, -r, or -x mode, list each file as it is\n"
161  " written or extracted. In -t mode, list more information about each\n"
162  " file.\n\n"
163 
164  " -z\n"
165  " Compress subfiles as they are written to the Multifile. Unlike tar\n"
166  " (but like zip), subfiles are compressed individually, instead of the\n"
167  " entire archive being compressed as one stream. It is not necessary\n"
168  " to specify -z when extracting compressed subfiles; they will always be\n"
169  " decompressed automatically. Also see -Z, which restricts which\n"
170  " subfiles will be compressed based on the filename extension.\n\n"
171 
172  " -e\n"
173  " Encrypt subfiles as they are written to the Multifile using the password\n"
174  " specified with -p, below. Subfiles are encrypted individually, rather\n"
175  " than encrypting the entire multifile, and different subfiles may be\n"
176  " encrypted using different passwords (although this requires running\n"
177  " multify multiple times). It is not possible to encrypt the multifile's\n"
178  " table of contents using this interface, but see the pencrypt program to\n"
179  " encrypt the entire multifile after it has been generated.\n\n"
180 
181 
182  " -p \"password\"\n"
183  " Specifies the password to encrypt or decrypt subfiles. If this is not\n"
184  " specified, and passwords are required, the user will be prompted from\n"
185  " standard input.\n\n"
186 
187  " -P \"prefix\"\n"
188  " Specifies a header_prefix to write to the beginning of the multifile.\n"
189  " This is primarily useful for creating a multifile that can be invoked\n"
190  " directly as a program from the shell on Unix-like environments,\n"
191  " for instance, p3d files. The header_prefix must begin with a hash\n"
192  " mark and end with a newline; this will be enforced if it is not\n"
193  " already so. This only has effect in conjunction with with -c, -u,\n"
194  " or -k.\n\n"
195 
196  " -F <scale_factor>\n"
197  " Specify a Multifile scale factor. This is only necessary to support\n"
198  " Multifiles that will exceed 4GB in size. The default scale factor is\n"
199  " 1, which should be sufficient for almost any application, but the total\n"
200  " size of the Multifile will be limited to 4GB * scale_factor. The size\n"
201  " of individual subfiles may not exceed 4GB in any case.\n\n"
202 
203  " -C <extract_dir>\n"
204 
205  " Change to the named directory before working on files;\n"
206  " that is, extraction/creation/update and replace will be based on this path\n\n"
207 
208  " -O\n"
209  " With -x, extract subfiles to standard output instead of to disk.\n\n"
210  " -Z <extension_list>\n"
211  " Specify a comma-separated list of filename extensions that represent\n"
212  " files that are not to be compressed. The default if this is omitted is\n"
213  " \"" << dont_compress_str << "\". Specify -Z \"\" (be sure to include the space) to allow\n"
214  " all files to be compressed.\n\n"
215  " -X <extension_list>\n"
216  " Specify a comma-separated list of filename extensions that represent\n"
217  " text files. These files are opened and read in text mode, and added to\n"
218  " the multifile with the text flag set. The default if this is omitted is\n"
219  " \"" << text_ext_str << "\". Specify -X \"\" (be sure to include the space) to record\n"
220  " all files in binary mode.\n\n"
221 
222  " -T <flag>\n"
223  " Enable or disable the recording of file timestamps within the multifile.\n"
224  " If <flag> is 1, timestamps will be recorded within the multifile for\n"
225  " each subfile added; this is the default behavior. If <flag> is 0,\n"
226  " timestamps will not be recorded, which will make it easier to do a\n"
227  " bitwise comparison between multifiles to determine whether their\n"
228  " contents are equivalent.\n\n"
229 
230  " -1 .. -9\n"
231  " Specify the compression level when -z is in effect. Larger numbers\n"
232  " generate slightly smaller files, but compression takes longer. The\n"
233  " default is -" << default_compression_level << ".\n\n"
234 
235  " -S file.crt[,chain.crt[,file.key[,\"password\"]]]\n"
236  " Sign the multifile. The signing certificate should be in PEM form in\n"
237  " file.crt, with its private key in PEM form in file.key. If the key\n"
238  " is encrypted on-disk, specify the decryption password as the third\n"
239  " option. If a certificate chain is required, chain.crt should also\n"
240  " be specified; note that the separating commas should be supplied\n"
241  " even if this optional filename is omitted.\n"
242  " You may also provide a single composite file that contains the\n"
243  " certificate, chain, and key in the same file.\n"
244  " PEM form is the form accepted by the Apache web server. The\n"
245  " signature is written to the multifile to prove it is unchanged; any\n"
246  " subsequent change to the multifile will invalidate the signature.\n"
247  " This parameter may be repeated to sign the multifile with additional\n"
248  " certificates.\n\n";
249 }
250 
251 const string &
252 get_password() {
253  if (!got_password) {
254  cerr << "Enter password: ";
255  getline(cin, password);
256  got_password = true;
257  }
258 
259  return password;
260 }
261 
262 
263 bool
264 is_named(const string &subfile_name, const vector_string &params) {
265  // Returns true if the indicated subfile appears on the list of
266  // files named on the command line.
267  if (params.empty()) {
268  // No named files; everything is listed.
269  return true;
270  }
271 
272  vector_string::const_iterator pi;
273  for (pi = params.begin(); pi != params.end(); ++pi) {
274  if (subfile_name == (*pi)) {
275  return true;
276  }
277  }
278 
279  return false;
280 }
281 
282 bool
283 is_text(const Filename &subfile_name) {
284  // Returns true if this filename should be read as a text file,
285  // false otherwise.
286 
287  string ext = subfile_name.get_extension();
288  if (text_ext.find(ext) != text_ext.end()) {
289  // This extension is listed on the -X parameter list; it's a text file.
290  return true;
291  }
292 
293  return false;
294 }
295 
296 int
297 get_compression_level(const Filename &subfile_name) {
298  // Returns the appropriate compression level for the named file.
299  if (!compress_flag) {
300  // Don't compress anything.
301  return 0;
302  }
303 
304  string ext = subfile_name.get_extension();
305  if (dont_compress.find(ext) != dont_compress.end()) {
306  // This extension is listed on the -Z parameter list; don't
307  // compress it.
308  return 0;
309  }
310 
311  // Go ahead and compress this file.
312  return default_compression_level;
313 }
314 
315 bool
316 do_add_files(Multifile *multifile, const pvector<Filename> &filenames);
317 
318 bool
319 do_add_directory(Multifile *multifile, const Filename &directory_name) {
320  vector_string files;
321  if (!directory_name.scan_directory(files)) {
322  cerr << "Unable to scan directory " << directory_name << "\n";
323  return false;
324  }
325 
326  pvector<Filename> filenames;
327  filenames.reserve(files.size());
328  vector_string::const_iterator fi;
329  for (fi = files.begin(); fi != files.end(); ++fi) {
330  Filename subfile_name(directory_name, (*fi));
331  filenames.push_back(subfile_name);
332  }
333 
334  return do_add_files(multifile, filenames);
335 }
336 
337 bool
338 do_add_files(Multifile *multifile, const pvector<Filename> &filenames) {
339  bool okflag = true;
341  for (fi = filenames.begin(); fi != filenames.end(); ++fi) {
342  Filename subfile_name = (*fi);
343 
344  if (subfile_name.is_directory()) {
345  if (!do_add_directory(multifile, subfile_name)) {
346  okflag = false;
347  }
348 
349  } else if (!subfile_name.exists()) {
350  cerr << "Not found: " << subfile_name << "\n";
351  okflag = false;
352 
353  } else {
354  if (is_text(subfile_name)) {
355  subfile_name.set_text();
356  } else {
357  subfile_name.set_binary();
358  }
359 
360  string new_subfile_name;
361  if (update) {
362  new_subfile_name = multifile->update_subfile
363  (subfile_name, subfile_name, get_compression_level(subfile_name));
364  } else {
365  new_subfile_name = multifile->add_subfile
366  (subfile_name, subfile_name, get_compression_level(subfile_name));
367  }
368  if (new_subfile_name.empty()) {
369  cerr << "Unable to add " << subfile_name << ".\n";
370  okflag = false;
371  } else {
372  if (verbose) {
373  cout << new_subfile_name << "\n";
374  }
375  }
376  }
377  }
378  return okflag;
379 }
380 
381 bool
382 add_files(const vector_string &params) {
383  PT(Multifile) multifile = new Multifile;
384  if (append || update) {
385  if (!multifile->open_read_write(multifile_name)) {
386  cerr << "Unable to open " << multifile_name << " for updating.\n";
387  return false;
388  }
389  } else {
390  if (!multifile->open_write(multifile_name)) {
391  cerr << "Unable to open " << multifile_name << " for writing.\n";
392  return false;
393  }
394  }
395 
396  if (got_record_timestamp_flag) {
397  multifile->set_record_timestamp(record_timestamp_flag);
398  }
399 
400  if (encryption_flag) {
401  multifile->set_encryption_flag(true);
402  multifile->set_encryption_password(get_password());
403  }
404 
405  if (got_header_prefix) {
406  multifile->set_header_prefix(header_prefix);
407  }
408 
409  if (scale_factor != 0 && scale_factor != multifile->get_scale_factor()) {
410  cerr << "Setting scale factor to " << scale_factor << "\n";
411  multifile->set_scale_factor(scale_factor);
412  }
413 
414  pvector<Filename> filenames;
415  filenames.reserve(params.size());
416  vector_string::const_iterator si;
417  for (si = params.begin(); si != params.end(); ++si) {
418  Filename subfile_name = Filename::from_os_specific(*si);
419  filenames.push_back(subfile_name);
420  }
421 
422  // Change current working directory, if requested.
423  if (got_chdir_to && !chdir_to.chdir()) {
424  cout << "Failed to chdir to " << chdir_to << ": " << strerror(errno) << endl;
425  return false;
426  }
427 
428  bool okflag = do_add_files(multifile, filenames);
429 
430  bool needs_repack = multifile->needs_repack();
431  if (append) {
432  // If we specified -r mode, we always repack.
433  needs_repack = true;
434  }
435 
436  if (needs_repack) {
437  if (!multifile->repack()) {
438  cerr << "Failed to write " << multifile_name << ".\n";
439  okflag = false;
440  }
441  } else {
442  if (!multifile->flush()) {
443  cerr << "Failed to write " << multifile_name << ".\n";
444  okflag = false;
445  }
446  }
447 
448  return okflag;
449 }
450 
451 bool
452 extract_files(const vector_string &params) {
453  if (!multifile_name.exists()) {
454  cerr << multifile_name << " not found.\n";
455  return false;
456  }
457  PT(Multifile) multifile = new Multifile;
458  if (!multifile->open_read(multifile_name)) {
459  cerr << "Unable to open " << multifile_name << " for reading.\n";
460  return false;
461  }
462 
463  int num_subfiles = multifile->get_num_subfiles();
464 
465  // First, check to see whether any of the named subfiles have been
466  // encrypted. If any have, we may need to prompt the user to enter
467  // a password before we can extract them.
468  int i;
469  bool any_encrypted = false;
470  for (i = 0; i < num_subfiles && !any_encrypted; i++) {
471  string subfile_name = multifile->get_subfile_name(i);
472  if (is_named(subfile_name, params)) {
473  if (multifile->is_subfile_encrypted(i)) {
474  any_encrypted = true;
475  }
476  }
477  }
478 
479  if (any_encrypted) {
480  multifile->set_encryption_password(get_password());
481  }
482 
483  // Now walk back through the list and this time do the extraction.
484  for (i = 0; i < num_subfiles; i++) {
485  string subfile_name = multifile->get_subfile_name(i);
486  if (is_named(subfile_name, params)) {
487  Filename filename = subfile_name;
488  if (got_chdir_to) {
489  filename = Filename(chdir_to, subfile_name);
490  }
491  if (to_stdout) {
492  if (verbose) {
493  cerr << filename << "\n";
494  }
495  multifile->extract_subfile_to(i, cout);
496  } else {
497  if (verbose) {
498  cout << filename << "\n";
499  }
500  multifile->extract_subfile(i, filename);
501  }
502  }
503  }
504 
505  return true;
506 }
507 
508 bool
509 kill_files(const vector_string &params) {
510  if (!multifile_name.exists()) {
511  cerr << multifile_name << " not found.\n";
512  return false;
513  }
514  PT(Multifile) multifile = new Multifile;
515  if (!multifile->open_read_write(multifile_name)) {
516  cerr << "Unable to open " << multifile_name << " for read/write.\n";
517  return false;
518  }
519 
520  if (got_header_prefix) {
521  multifile->set_header_prefix(header_prefix);
522  }
523 
524  int i = 0;
525  while (i < multifile->get_num_subfiles()) {
526  string subfile_name = multifile->get_subfile_name(i);
527  if (is_named(subfile_name, params)) {
528  Filename filename = subfile_name;
529 
530  if (verbose) {
531  cout << filename << "\n";
532  }
533  multifile->remove_subfile(i);
534  } else {
535  ++i;
536  }
537  }
538 
539  bool okflag = true;
540 
541  if (multifile->needs_repack()) {
542  if (!multifile->repack()) {
543  cerr << "Failed to write " << multifile_name << ".\n";
544  okflag = false;
545  }
546  } else {
547  if (!multifile->flush()) {
548  cerr << "Failed to write " << multifile_name << ".\n";
549  okflag = false;
550  }
551  }
552 
553  return okflag;
554 }
555 
556 bool
557 sign_multifile() {
558 #ifndef HAVE_OPENSSL
559  cerr << "Cannot sign multifiles without OpenSSL compiled in.\n";
560  return false;
561 
562 #else // HAVE_OPENSSL
563  // Re-open the Multifile, and sign it with the indicated certificate
564  // and key files.
565  PT(Multifile) multifile = new Multifile;
566  if (!multifile->open_read_write(multifile_name)) {
567  cerr << "Unable to re-open " << multifile_name << " for signing.\n";
568  return false;
569  }
570 
571  vector_string::iterator si;
572  for (si = sign_params.begin(); si != sign_params.end(); ++si) {
573  const string &param = (*si);
574  Filename certificate, chain, pkey;
575  string password;
576 
577  size_t comma1 = param.find(',');
578  if (comma1 == string::npos) {
579  certificate = Filename::from_os_specific(param);
580  } else {
581  certificate = Filename::from_os_specific(param.substr(0, comma1));
582  size_t comma2 = param.find(',', comma1 + 1);
583  if (comma2 == string::npos) {
584  chain = Filename::from_os_specific(param.substr(comma1 + 1));
585  } else {
586  chain = Filename::from_os_specific(param.substr(comma1 + 1, comma2 - comma1 - 1));
587  size_t comma3 = param.find(',', comma2 + 1);
588  if (comma3 == string::npos) {
589  pkey = Filename::from_os_specific(param.substr(comma2 + 1));
590  } else {
591  pkey = Filename::from_os_specific(param.substr(comma2 + 1, comma3 - comma2 - 1));
592  password = param.substr(comma3 + 1);
593  }
594  }
595  }
596 
597  if (!multifile->add_signature(certificate, chain, pkey, password)) {
598  return false;
599  }
600  }
601 
602  return true;
603 #endif // HAVE_OPENSSL
604 }
605 
606 const char *
607 format_timestamp(bool record_timestamp, time_t timestamp) {
608  static const size_t buffer_size = 512;
609  static char buffer[buffer_size];
610 
611  if (!record_timestamp) {
612  // No timestamps.
613  return "";
614  }
615 
616  if (timestamp == 0) {
617  // A zero timestamp is a special case.
618  return " (no date) ";
619  }
620 
621  time_t now = time(NULL);
622  struct tm *tm_p = localtime(&timestamp);
623 
624  if (timestamp > now || (now - timestamp > 86400 * 365)) {
625  // A timestamp in the future, or more than a year in the past,
626  // gets a year appended.
627  strftime(buffer, buffer_size, "%b %d %Y", tm_p);
628  } else {
629  // Otherwise, within the past year, show the date and time.
630  strftime(buffer, buffer_size, "%b %d %H:%M", tm_p);
631  }
632 
633  return buffer;
634 }
635 
636 bool
637 list_files(const vector_string &params) {
638  if (!multifile_name.exists()) {
639  cerr << multifile_name << " not found.\n";
640  return false;
641  }
642  PT(Multifile) multifile = new Multifile;
643  if (!multifile->open_read(multifile_name)) {
644  cerr << "Unable to open " << multifile_name << " for reading.\n";
645  return false;
646  }
647 
648  int num_subfiles = multifile->get_num_subfiles();
649 
650  int i;
651  if (verbose) {
652  cout << num_subfiles << " subfiles:\n" << flush;
653  for (i = 0; i < num_subfiles; i++) {
654  string subfile_name = multifile->get_subfile_name(i);
655  if (is_named(subfile_name, params)) {
656  char encrypted_symbol = ' ';
657  if (multifile->is_subfile_encrypted(i)) {
658  encrypted_symbol = 'e';
659  }
660  char text_symbol = ' ';
661  if (multifile->is_subfile_text(i)) {
662  text_symbol = 't';
663  }
664  if (multifile->is_subfile_compressed(i)) {
665  size_t orig_length = multifile->get_subfile_length(i);
666  size_t internal_length = multifile->get_subfile_internal_length(i);
667  double ratio = 1.0;
668  if (orig_length != 0) {
669  ratio = (double)internal_length / (double)orig_length;
670  }
671  if (ratio > 1.0) {
672  printf("%12d worse %c%c %s %s\n",
673  (int)multifile->get_subfile_length(i),
674  encrypted_symbol, text_symbol,
675  format_timestamp(multifile->get_record_timestamp(),
676  multifile->get_subfile_timestamp(i)),
677  subfile_name.c_str());
678  } else {
679  printf("%12d %3.0f%% %c%c %s %s\n",
680  (int)multifile->get_subfile_length(i),
681  100.0 - ratio * 100.0,
682  encrypted_symbol, text_symbol,
683  format_timestamp(multifile->get_record_timestamp(),
684  multifile->get_subfile_timestamp(i)),
685  subfile_name.c_str());
686  }
687  } else {
688  printf("%12d %c%c %s %s\n",
689  (int)multifile->get_subfile_length(i),
690  encrypted_symbol, text_symbol,
691  format_timestamp(multifile->get_record_timestamp(),
692  multifile->get_subfile_timestamp(i)),
693  subfile_name.c_str());
694  }
695  }
696  }
697  fflush(stdout);
698 
699  if (multifile->get_record_timestamp()) {
700  cout << "Last modification "
701  << format_timestamp(true, multifile->get_timestamp()) << "\n";
702  }
703 
704  if (multifile->get_scale_factor() != 1) {
705  cout << "Scale factor is " << multifile->get_scale_factor() << "\n";
706  }
707  if (multifile->needs_repack()) {
708  cout << "Multifile needs to be repacked.\n";
709  }
710  } else {
711  for (i = 0; i < num_subfiles; i++) {
712  string subfile_name = multifile->get_subfile_name(i);
713  if (is_named(subfile_name, params)) {
714  cout << subfile_name << "\n";
715  }
716  }
717  }
718 
719 #ifdef HAVE_OPENSSL
720  int num_signatures = multifile->get_num_signatures();
721  if (num_signatures != 0) {
722  cout << "\n";
723  for (i = 0; i < num_signatures; ++i) {
724  cout << "Signed by " << multifile->get_signature_friendly_name(i);
725  int verify_result = multifile->validate_signature_certificate(i);
726  if (verify_result == 0) {
727  cout << " (certificate validated)\n";
728  } else {
729  cout << " (certificate unknown, reason " << verify_result << ")\n";
730  }
731  if (verbose) {
732  multifile->write_signature_certificate(i, cout);
733  cout << "\n";
734  }
735  }
736  }
737 #endif // HAVE_OPENSSL
738 
739  return true;
740 }
741 
742 void
743 tokenize_extensions(const string &str, pset<string> &extensions) {
744  size_t p = 0;
745  while (p < str.length()) {
746  size_t q = str.find_first_of(",", p);
747  if (q == string::npos) {
748  extensions.insert(str.substr(p));
749  return;
750  }
751  extensions.insert(str.substr(p, q - p));
752  p = q + 1;
753  }
754  extensions.insert(string());
755 }
756 
757 int
758 main(int argc, char **argv) {
759  // A call to pystub() to force libpystub.so to be linked in.
760  pystub();
761 
762  preprocess_argv(argc, argv);
763  if (argc < 2) {
764  usage();
765  return 1;
766  }
767 
768  // To emulate tar, we assume an implicit hyphen in front of the
769  // first argument if there is not one already.
770  if (argc >= 2) {
771  if (*argv[1] != '-' && *argv[1] != '\0') {
772  char *new_arg = (char *)PANDA_MALLOC_ARRAY(strlen(argv[1]) + 2);
773  new_arg[0] = '-';
774  strcpy(new_arg + 1, argv[1]);
775  argv[1] = new_arg;
776  }
777  }
778 
779  extern char *optarg;
780  extern int optind;
781  static const char *optflags = "crutxkvz123456789Z:T:X:S:f:OC:ep:P:F:h";
782  int flag = getopt(argc, argv, optflags);
783  Filename rel_path;
784  while (flag != EOF) {
785  switch (flag) {
786  case 'c':
787  create = true;
788  break;
789  case 'r':
790  append = true;
791  break;
792  case 'u':
793  update = true;
794  break;
795  case 't':
796  tlist = true;
797  break;
798  case 'x':
799  extract = true;
800  break;
801  case 'k':
802  kill_cmd = true;
803  break;
804  case 'v':
805  verbose = true;
806  break;
807  case 'z':
808  compress_flag = true;
809  break;
810  case '1':
811  default_compression_level = 1;
812  compress_flag = true;
813  break;
814  case '2':
815  default_compression_level = 2;
816  compress_flag = true;
817  break;
818  case '3':
819  default_compression_level = 3;
820  compress_flag = true;
821  break;
822  case '4':
823  default_compression_level = 4;
824  compress_flag = true;
825  break;
826  case '5':
827  default_compression_level = 5;
828  compress_flag = true;
829  break;
830  case '6':
831  default_compression_level = 6;
832  compress_flag = true;
833  break;
834  case '7':
835  default_compression_level = 7;
836  compress_flag = true;
837  break;
838  case '8':
839  default_compression_level = 8;
840  compress_flag = true;
841  break;
842  case '9':
843  default_compression_level = 9;
844  compress_flag = true;
845  break;
846  case 'Z':
847  dont_compress_str = optarg;
848  break;
849  case 'X':
850  text_ext_str = optarg;
851  break;
852  case 'S':
853  sign_params.push_back(optarg);
854  break;
855  case 'T':
856  {
857  int flag;
858  if (!string_to_int(optarg, flag) ||
859  (flag != 0 && flag != 1)) {
860  cerr << "Invalid timestamp flag: " << optarg << "\n";
861  usage();
862  return 1;
863  }
864  record_timestamp_flag = (flag != 0);
865  got_record_timestamp_flag = true;
866  }
867  break;
868  case 'f':
869  multifile_name = Filename::from_os_specific(optarg);
870  got_multifile_name = true;
871  break;
872  case 'C':
873  chdir_to = Filename::from_os_specific(optarg);
874  got_chdir_to = true;
875  break;
876  case 'O':
877  to_stdout = true;
878  break;
879  case 'e':
880  encryption_flag = true;
881  break;
882  case 'p':
883  password = optarg;
884  got_password = true;
885  break;
886  case 'P':
887  header_prefix = optarg;
888  got_header_prefix = true;
889  break;
890  case 'F':
891  {
892  char *endptr;
893  scale_factor = strtol(optarg, &endptr, 10);
894  if (*endptr != '\0') {
895  cerr << "Invalid integer: " << optarg << "\n";
896  usage();
897  return 1;
898  }
899  if (scale_factor == 0) {
900  cerr << "Scale factor must be nonzero.\n";
901  usage();
902  return 1;
903  }
904  }
905  break;
906 
907  case 'h':
908  help();
909  return 1;
910  case '?':
911  usage();
912  return 1;
913  default:
914  cerr << "Unhandled switch: " << flag << endl;
915  break;
916  }
917  flag = getopt(argc, argv, optflags);
918  }
919  argc -= (optind - 1);
920  argv += (optind - 1);
921 
922  // We should have exactly one of these options.
923  if ((create?1:0) + (append?1:0) + (update?1:0) + (tlist?1:0) + (extract?1:0) + (kill_cmd?1:0) != 1) {
924  cerr << "Exactly one of -c, -r, -u, -t, -x, -k must be specified.\n";
925  usage();
926  return 1;
927  }
928 
929  if (!got_multifile_name) {
930  cerr << "Multifile name not specified.\n";
931  usage();
932  return 1;
933  }
934 
935  // Split out the extensions named by -Z into different words.
936  tokenize_extensions(dont_compress_str, dont_compress);
937 
938  // Ditto for -X.
939  tokenize_extensions(text_ext_str, text_ext);
940 
941  // Build a list of remaining parameters.
942  vector_string params;
943  params.reserve(argc - 1);
944  for (int i = 1; i < argc; i++) {
945  params.push_back(argv[i]);
946  }
947 
948  bool okflag = true;
949  if (create || append || update) {
950  okflag = add_files(params);
951  } else if (extract) {
952  if (got_record_timestamp_flag) {
953  cerr << "Warning: -T ignored on extract.\n";
954  }
955  okflag = extract_files(params);
956  } else if (kill_cmd) {
957  if (got_record_timestamp_flag) {
958  cerr << "Warning: -T ignored on kill.\n";
959  }
960  okflag = kill_files(params);
961  } else { // list
962  if (got_record_timestamp_flag) {
963  cerr << "Warning: -T ignored on list.\n";
964  }
965  okflag = list_files(params);
966  }
967 
968  if (okflag && !sign_params.empty()) {
969  sign_multifile();
970  }
971 
972  if (okflag) {
973  return 0;
974  } else {
975  return 1;
976  }
977 }
bool is_subfile_compressed(int index) const
Returns true if the indicated subfile has been compressed when stored within the archive, false otherwise.
Definition: multifile.cxx:1736
time_t get_subfile_timestamp(int index) const
Returns the modification time of the nth subfile.
Definition: multifile.cxx:1719
bool is_directory() const
Returns true if the filename exists and is a directory name, false otherwise.
Definition: filename.cxx:1456
void set_encryption_password(const string &encryption_password)
Specifies the password that will be used to encrypt subfiles subsequently added to the multifile...
Definition: multifile.I:190
bool open_write(const Filename &multifile_name)
Opens the named Multifile on disk for writing.
Definition: multifile.cxx:252
void set_binary()
Indicates that the filename represents a binary file.
Definition: filename.I:494
void set_text()
Indicates that the filename represents a text file.
Definition: filename.I:507
bool chdir() const
Changes directory to the specified location.
Definition: filename.cxx:2535
void set_header_prefix(const string &header_prefix)
Sets the string which is written to the Multifile before the Multifile header.
Definition: multifile.cxx:2111
void set_encryption_flag(bool flag)
Sets the flag indicating whether subsequently-added subfiles should be encrypted before writing them ...
Definition: multifile.I:152
bool scan_directory(vector_string &contents) const
Attempts to open the named filename as if it were a directory and looks for the non-hidden files with...
Definition: filename.cxx:1854
string update_subfile(const string &subfile_name, const Filename &filename, int compression_level)
Adds a file on disk to the subfile.
Definition: multifile.cxx:564
bool get_record_timestamp() const
Returns the flag indicating whether timestamps should be recorded within the Multifile or not...
Definition: multifile.I:121
bool repack()
Forces a complete rewrite of the Multifile and all of its contents, so that its index will appear at ...
Definition: multifile.cxx:1470
size_t get_scale_factor() const
Returns the internal scale factor for this Multifile.
Definition: multifile.I:132
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:44
bool is_subfile_text(int index) const
Returns true if the indicated subfile represents text data, or false if it represents binary data...
Definition: multifile.cxx:1766
void set_scale_factor(size_t scale_factor)
Changes the internal scale factor for this Multifile.
Definition: multifile.cxx:442
bool open_read_write(const Filename &multifile_name)
Opens the named Multifile on disk for reading and writing.
Definition: multifile.cxx:303
string add_subfile(const string &subfile_name, const Filename &filename, int compression_level)
Adds a file on disk as a subfile to the Multifile.
Definition: multifile.cxx:480
bool flush()
Writes all contents of the Multifile to disk.
Definition: multifile.cxx:1309
bool extract_subfile(int index, const Filename &filename)
Extracts the nth subfile into a file with the given name.
Definition: multifile.cxx:1898
const string & get_subfile_name(int index) const
Returns the name of the nth subfile.
Definition: multifile.cxx:1686
void set_record_timestamp(bool record_timestamp)
Sets the flag indicating whether timestamps should be recorded within the Multifile or not...
Definition: multifile.I:108
void remove_subfile(int index)
Removes the nth subfile from the Multifile.
Definition: multifile.cxx:1666
size_t get_subfile_length(int index) const
Returns the uncompressed data length of the nth subfile.
Definition: multifile.cxx:1703
bool exists() const
Returns true if the filename exists on the disk, false otherwise.
Definition: filename.cxx:1356
bool extract_subfile_to(int index, ostream &out)
Extracts the nth subfile to the indicated ostream.
Definition: multifile.cxx:1933
int get_num_subfiles() const
Returns the number of subfiles within the Multifile.
Definition: multifile.cxx:1549
A file that contains a set of files.
Definition: multifile.h:34
size_t get_subfile_internal_length(int index) const
Returns the number of bytes the indicated subfile consumes within the archive.
Definition: multifile.cxx:1818
bool is_subfile_encrypted(int index) const
Returns true if the indicated subfile has been encrypted when stored within the archive, false otherwise.
Definition: multifile.cxx:1749
time_t get_timestamp() const
Returns the modification timestamp of the overall Multifile.
Definition: multifile.I:86
string get_extension() const
Returns the file extension.
Definition: filename.I:477
bool needs_repack() const
Returns true if the Multifile index is suboptimal and should be repacked.
Definition: multifile.I:71
static Filename from_os_specific(const string &os_specific, Type type=T_general)
This named constructor returns a Panda-style filename (that is, using forward slashes, and no drive letter) based on the supplied filename string that describes a filename in the local system conventions (for instance, on Windows, it may use backslashes or begin with a drive letter and a colon).
Definition: filename.cxx:332