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