Panda3D
Loading...
Searching...
No Matches
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
24using std::cerr;
25using std::cout;
26using std::endl;
27using std::string;
28
29
30bool create = false; // -c
31bool append = false; // -r
32bool update = false; // -u
33bool tlist = false; // -t
34bool extract = false; // -x
35bool kill_cmd = false; // -k
36bool verbose = false; // -v
37bool compress_flag = false; // -z
38int default_compression_level = 6;
39Filename multifile_name; // -f
40bool got_multifile_name = false;
41bool to_stdout = false; // -O
42bool encryption_flag = false; // -e
43string password; // -p
44bool got_password = false;
45string header_prefix; // -P
46bool got_header_prefix = false;
47Filename chdir_to; // -C
48bool got_chdir_to = false;
49size_t scale_factor = 0; // -F
50pset<string> dont_compress; // -Z
51pset<string> text_ext; // -X
52vector_string sign_params; // -S
53
54// Default extensions not to compress. May be overridden with -Z.
55string dont_compress_str = "jpg,png,mp3,ogg";
56
57// Default text extensions. May be overridden with -X.
58string text_ext_str = "txt";
59
60time_t source_date_epoch = (time_t)-1;
61bool got_record_timestamp_flag = false;
62bool 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 */
73static int
74string_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 */
87static bool
88string_to_int(const string &str, int &result) {
89 string tail;
90 result = string_to_int(str, tail);
91 return tail.empty();
92}
93
94void
95usage() {
96 cerr <<
97 "Usage: multify -[c|r|u|t|x] -f <multifile_name> [options] <subfile_name> ...\n";
98}
99
100void
101help() {
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
250const string &
251get_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
262bool
263is_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
281bool
282is_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
295int
296get_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
313bool
314do_add_files(Multifile *multifile, const pvector<Filename> &filenames);
315
316bool
317do_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
335bool
336do_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
379bool
380add_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
455bool
456extract_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
512bool
513kill_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
566bool
567sign_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
616const char *
617format_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
646bool
647list_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
761void
762tokenize_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
776int
777main(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:44
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...
void set_binary()
Indicates that the filename represents a binary file.
Definition filename.I:414
bool chdir() const
Changes directory to the specified location.
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 on the physical disk and is a directory name, false otherwise.
std::string get_extension() const
Returns the file extension.
Definition filename.I:400
bool exists() const
Returns true if the filename exists on the physical disk, false otherwise.
This class provides a locking wrapper around an arbitrary istream pointer.
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.
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.
bool open_read_write(const Filename &multifile_name)
Opens the named Multifile on disk for reading and writing.
std::string update_subfile(const std::string &subfile_name, const Filename &filename, int compression_level)
Adds a file on disk to the subfile.
bool extract_subfile_to(int index, std::ostream &out)
Extracts the nth subfile to the indicated ostream.
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.
bool is_subfile_compressed(int index) const
Returns true if the indicated subfile has been compressed when stored within the archive,...
time_t get_subfile_timestamp(int index) const
Returns the modification time of the nth subfile.
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.
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.
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,...
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 ...
bool open_write(const Filename &multifile_name)
Opens the named Multifile on disk for writing.
size_t get_subfile_internal_length(int index) const
Returns the number of bytes the indicated subfile consumes within the archive.
void remove_subfile(int index)
Removes the nth subfile from the Multifile.
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.
bool extract_subfile(int index, const Filename &filename)
Extracts the nth subfile into a file with the given name.
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
This is our own Panda specialization on the default STL vector.
Definition pvector.h:42
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.