426 lines
12 KiB
Text
426 lines
12 KiB
Text
|
/**
|
||
|
* PANDA 3D SOFTWARE
|
||
|
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||
|
*
|
||
|
* All use of this software is subject to the terms of the revised BSD
|
||
|
* license. You should have received a copy of this license along
|
||
|
* with this source code in a file named "LICENSE."
|
||
|
*
|
||
|
* @file multifile.I
|
||
|
* @author mike
|
||
|
* @date 1997-01-09
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Returns the filename of the Multifile, if it is available.
|
||
|
*/
|
||
|
INLINE const Filename &Multifile::
|
||
|
get_multifile_name() const {
|
||
|
return _multifile_name;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Replaces the filename of the Multifile. This is primarily used for
|
||
|
* documentation purposes only; changing this name does not open the indicated
|
||
|
* file. See open_read() or open_write() for that.
|
||
|
*/
|
||
|
INLINE void Multifile::
|
||
|
set_multifile_name(const Filename &multifile_name) {
|
||
|
_multifile_name = multifile_name;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns true if the Multifile has been opened for read mode and there have
|
||
|
* been no errors, and individual Subfile contents may be extracted.
|
||
|
*/
|
||
|
INLINE bool Multifile::
|
||
|
is_read_valid() const {
|
||
|
return (_read != nullptr);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns true if the Multifile has been opened for write mode and there have
|
||
|
* been no errors, and Subfiles may be added or removed from the Multifile.
|
||
|
*/
|
||
|
INLINE bool Multifile::
|
||
|
is_write_valid() const {
|
||
|
return (_write != nullptr && !_write->fail());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns true if the Multifile index is suboptimal and should be repacked.
|
||
|
* Call repack() to achieve this.
|
||
|
*/
|
||
|
INLINE bool Multifile::
|
||
|
needs_repack() const {
|
||
|
return _needs_repack || (_scale_factor != _new_scale_factor);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the modification timestamp of the overall Multifile. This
|
||
|
* indicates the most recent date at which subfiles were added or removed from
|
||
|
* the Multifile. Note that it is logically possible for an individual
|
||
|
* subfile to have a more recent timestamp than the overall timestamp.
|
||
|
*/
|
||
|
INLINE time_t Multifile::
|
||
|
get_timestamp() const {
|
||
|
return _timestamp;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the flag indicating whether timestamps should be recorded within the
|
||
|
* Multifile or not. The default is true, indicating the Multifile will
|
||
|
* record timestamps for the overall file and also for each subfile.
|
||
|
*
|
||
|
* If this is false, the Multifile will not record timestamps internally. In
|
||
|
* this case, the return value from get_timestamp() or get_subfile_timestamp()
|
||
|
* will be estimations.
|
||
|
*
|
||
|
* You may want to set this false to minimize the bitwise difference between
|
||
|
* independently-generated Multifiles.
|
||
|
*/
|
||
|
INLINE void Multifile::
|
||
|
set_record_timestamp(bool flag) {
|
||
|
_record_timestamp = flag;
|
||
|
_timestamp_dirty = true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the flag indicating whether timestamps should be recorded within
|
||
|
* the Multifile or not. See set_record_timestamp().
|
||
|
*/
|
||
|
INLINE bool Multifile::
|
||
|
get_record_timestamp() const {
|
||
|
return _record_timestamp;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the internal scale factor for this Multifile. See
|
||
|
* set_scale_factor().
|
||
|
*/
|
||
|
INLINE size_t Multifile::
|
||
|
get_scale_factor() const {
|
||
|
return _new_scale_factor;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the flag indicating whether subsequently-added subfiles should be
|
||
|
* encrypted before writing them to the multifile. If true, subfiles will be
|
||
|
* encrypted; if false (the default), they will be written without encryption.
|
||
|
*
|
||
|
* When true, subfiles will be encrypted with the password specified by
|
||
|
* set_encryption_password(). It is possible to apply a different password to
|
||
|
* different files, but the resulting file can't be mounted via VFS.
|
||
|
*/
|
||
|
INLINE void Multifile::
|
||
|
set_encryption_flag(bool flag) {
|
||
|
#ifndef HAVE_OPENSSL
|
||
|
if (flag) {
|
||
|
express_cat.warning()
|
||
|
<< "OpenSSL not compiled in; cannot generated encrypted multifiles.\n";
|
||
|
flag = false;
|
||
|
}
|
||
|
#endif // HAVE_OPENSSL
|
||
|
_encryption_flag = flag;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the flag indicating whether subsequently-added subfiles should be
|
||
|
* encrypted before writing them to the multifile. See set_encryption_flag().
|
||
|
*/
|
||
|
INLINE bool Multifile::
|
||
|
get_encryption_flag() const {
|
||
|
return _encryption_flag;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Specifies the password that will be used to encrypt subfiles subsequently
|
||
|
* added to the multifile, if the encryption flag is also set true (see
|
||
|
* set_encryption_flag()).
|
||
|
*
|
||
|
* It is possible to apply a different password to different files, but the
|
||
|
* resulting file can't be mounted via VFS. Changing this value may cause an
|
||
|
* implicit call to flush().
|
||
|
*/
|
||
|
INLINE void Multifile::
|
||
|
set_encryption_password(const std::string &encryption_password) {
|
||
|
if (_encryption_password != encryption_password) {
|
||
|
if (!_new_subfiles.empty()) {
|
||
|
flush();
|
||
|
}
|
||
|
_encryption_password = encryption_password;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the password that will be used to encrypt subfiles subsequently
|
||
|
* added to the multifile. See set_encryption_password().
|
||
|
*/
|
||
|
INLINE const std::string &Multifile::
|
||
|
get_encryption_password() const {
|
||
|
return _encryption_password;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Specifies the encryption algorithm that should be used for future calls to
|
||
|
* add_subfile(). The default is whatever is specified by the encryption-
|
||
|
* algorithm config variable. The complete set of available algorithms is
|
||
|
* defined by the current version of OpenSSL.
|
||
|
*
|
||
|
* If an invalid algorithm is specified, there is no immediate error return
|
||
|
* code, but flush() will fail and the file will be invalid.
|
||
|
*
|
||
|
* It is possible to apply a different encryption algorithm to different
|
||
|
* files, and unlike the password, this does not interfere with mounting the
|
||
|
* multifile via VFS. Changing this value may cause an implicit call to
|
||
|
* flush().
|
||
|
*/
|
||
|
INLINE void Multifile::
|
||
|
set_encryption_algorithm(const std::string &encryption_algorithm) {
|
||
|
if (_encryption_algorithm != encryption_algorithm) {
|
||
|
if (!_new_subfiles.empty()) {
|
||
|
flush();
|
||
|
}
|
||
|
_encryption_algorithm = encryption_algorithm;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the encryption algorithm that was specified by
|
||
|
* set_encryption_algorithm().
|
||
|
*/
|
||
|
INLINE const std::string &Multifile::
|
||
|
get_encryption_algorithm() const {
|
||
|
return _encryption_algorithm;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Specifies the length of the key, in bits, that should be used to encrypt
|
||
|
* the stream in future calls to add_subfile(). The default is whatever is
|
||
|
* specified by the encryption-key-length config variable.
|
||
|
*
|
||
|
* If an invalid key_length for the chosen algorithm is specified, there is no
|
||
|
* immediate error return code, but flush() will fail and the file will be
|
||
|
* invalid.
|
||
|
*
|
||
|
* It is possible to apply a different key length to different files, and
|
||
|
* unlike the password, this does not interfere with mounting the multifile
|
||
|
* via VFS. Changing this value may cause an implicit call to flush().
|
||
|
*/
|
||
|
INLINE void Multifile::
|
||
|
set_encryption_key_length(int encryption_key_length) {
|
||
|
if (_encryption_key_length != encryption_key_length) {
|
||
|
if (!_new_subfiles.empty()) {
|
||
|
flush();
|
||
|
}
|
||
|
_encryption_key_length = encryption_key_length;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the encryption key length, in bits, that was specified by
|
||
|
* set_encryption_key_length().
|
||
|
*/
|
||
|
INLINE int Multifile::
|
||
|
get_encryption_key_length() const {
|
||
|
return _encryption_key_length;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Specifies the number of times to repeatedly hash the key before writing it
|
||
|
* to the stream in future calls to add_subfile(). Its purpose is to make it
|
||
|
* computationally more expensive for an attacker to search the key space
|
||
|
* exhaustively. This should be a multiple of 1,000 and should not exceed
|
||
|
* about 65 million; the value 0 indicates just one application of the hashing
|
||
|
* algorithm.
|
||
|
*
|
||
|
* The default is whatever is specified by the multifile-encryption-iteration-
|
||
|
* count config variable.
|
||
|
*
|
||
|
* It is possible to apply a different iteration count to different files, and
|
||
|
* unlike the password, this does not interfere with mounting the multifile
|
||
|
* via VFS. Changing this value causes an implicit call to flush().
|
||
|
*/
|
||
|
INLINE void Multifile::
|
||
|
set_encryption_iteration_count(int encryption_iteration_count) {
|
||
|
if (_encryption_iteration_count != encryption_iteration_count) {
|
||
|
flush();
|
||
|
_encryption_iteration_count = encryption_iteration_count;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the value that was specified by set_encryption_iteration_count().
|
||
|
*/
|
||
|
INLINE int Multifile::
|
||
|
get_encryption_iteration_count() const {
|
||
|
return _encryption_iteration_count;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Removes the named subfile from the Multifile, if it exists; returns true if
|
||
|
* successfully removed, or false if it did not exist in the first place. The
|
||
|
* file will not actually be removed from the disk until the next call to
|
||
|
* flush().
|
||
|
*
|
||
|
* Note that this does not actually remove the data from the indicated
|
||
|
* subfile; it simply removes it from the index. The Multifile will not be
|
||
|
* reduced in size after this operation, until the next call to repack().
|
||
|
*/
|
||
|
INLINE bool Multifile::
|
||
|
remove_subfile(const std::string &subfile_name) {
|
||
|
int index = find_subfile(subfile_name);
|
||
|
if (index >= 0) {
|
||
|
remove_subfile(index);
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a vector_uchar that contains the entire contents of the indicated
|
||
|
* subfile.
|
||
|
*/
|
||
|
INLINE vector_uchar Multifile::
|
||
|
read_subfile(int index) {
|
||
|
vector_uchar result;
|
||
|
read_subfile(index, result);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a string with the first n bytes written to a Multifile, to identify
|
||
|
* it as a Multifile.
|
||
|
*/
|
||
|
INLINE std::string Multifile::
|
||
|
get_magic_number() {
|
||
|
return std::string(_header, _header_size);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the string that preceded the Multifile header on the file, if any.
|
||
|
* See set_header_prefix().
|
||
|
*/
|
||
|
INLINE const std::string &Multifile::
|
||
|
get_header_prefix() const {
|
||
|
return _header_prefix;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Converts a size_t address read from the file to a streampos byte address
|
||
|
* within the file.
|
||
|
*/
|
||
|
INLINE std::streampos Multifile::
|
||
|
word_to_streampos(size_t word) const {
|
||
|
return (std::streampos)word * (std::streampos)_scale_factor;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Converts a streampos byte address within the file to a size_t value
|
||
|
* suitable for writing to the file.
|
||
|
*/
|
||
|
INLINE size_t Multifile::
|
||
|
streampos_to_word(std::streampos fpos) const {
|
||
|
return (size_t)((fpos + (std::streampos)_scale_factor - (std::streampos)1) / (std::streampos)_scale_factor);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Rounds the streampos byte address up to the next multiple of _scale_factor.
|
||
|
* Only multiples of _scale_factor may be written to the file.
|
||
|
*/
|
||
|
INLINE std::streampos Multifile::
|
||
|
normalize_streampos(std::streampos fpos) const {
|
||
|
return word_to_streampos(streampos_to_word(fpos));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Converts a single nibble to a hex digit.
|
||
|
*/
|
||
|
INLINE char Multifile::
|
||
|
tohex(unsigned int nibble) {
|
||
|
nibble &= 0xf;
|
||
|
if (nibble < 10) {
|
||
|
return nibble + '0';
|
||
|
}
|
||
|
return nibble - 10 + 'a';
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
*/
|
||
|
INLINE Multifile::Subfile::
|
||
|
Subfile() {
|
||
|
_index_start = 0;
|
||
|
_data_start = 0;
|
||
|
_data_length = 0;
|
||
|
_timestamp = 0;
|
||
|
_source = nullptr;
|
||
|
_flags = 0;
|
||
|
_compression_level = 0;
|
||
|
#ifdef HAVE_OPENSSL
|
||
|
_pkey = nullptr;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Compares two Subfiles for proper sorting within the index.
|
||
|
*/
|
||
|
INLINE bool Multifile::Subfile::
|
||
|
operator < (const Multifile::Subfile &other) const {
|
||
|
// This should only be called on normal subfiles, not on certificate files
|
||
|
// or signature files. (We don't attempt to sort these special signature
|
||
|
// files.)
|
||
|
nassertr(!is_cert_special() && !other.is_cert_special(), false);
|
||
|
|
||
|
// Normal subfiles are simply sorted in alphabetical order by filename.
|
||
|
return _name < other._name;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns true if the Subfile indicates it has been deleted (removed from the
|
||
|
* index), false otherwise. This should never be true of Subfiles that
|
||
|
* currently appear in either the _subfiles or _new_subfiles lists.
|
||
|
*/
|
||
|
INLINE bool Multifile::Subfile::
|
||
|
is_deleted() const {
|
||
|
return (_flags & SF_deleted) != 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns true if there was some problem reading the index record for this
|
||
|
* Subfile from the Multifile.
|
||
|
*/
|
||
|
INLINE bool Multifile::Subfile::
|
||
|
is_index_invalid() const {
|
||
|
return (_flags & SF_index_invalid) != 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns true if there was some problem reading the data contents of this
|
||
|
* Subfile, particularly when copying into the Multifile.
|
||
|
*/
|
||
|
INLINE bool Multifile::Subfile::
|
||
|
is_data_invalid() const {
|
||
|
return (_flags & SF_data_invalid) != 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns true if this Subfile represents a signature record, which is
|
||
|
* treated specially; or false if it is an ordinary Subfile.
|
||
|
*/
|
||
|
INLINE bool Multifile::Subfile::
|
||
|
is_cert_special() const {
|
||
|
return (_flags & SF_signature) != 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the byte position within the Multifile of the last byte that
|
||
|
* contributes to this Subfile, either in the index record or in the subfile
|
||
|
* data.
|
||
|
*/
|
||
|
INLINE std::streampos Multifile::Subfile::
|
||
|
get_last_byte_pos() const {
|
||
|
return std::max(_index_start + (std::streampos)_index_length,
|
||
|
_data_start + (std::streampos)_data_length) - (std::streampos)1;
|
||
|
}
|