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