604 lines
21 KiB
C++
604 lines
21 KiB
C++
/* Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License, version 2.0,
|
|
as published by the Free Software Foundation.
|
|
|
|
This program is also distributed with certain software (including
|
|
but not limited to OpenSSL) that is licensed under separate terms,
|
|
as designated in a particular file or component or in included license
|
|
documentation. The authors of MySQL hereby grant you an additional
|
|
permission to link the program and your derivative works with the
|
|
separately licensed software that they have included with MySQL.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License, version 2.0, for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
|
|
|
|
#include "plugin/keyring/buffered_file_io.h"
|
|
|
|
#include <fcntl.h>
|
|
#include <mysql/psi/mysql_file.h>
|
|
#include <stdio.h>
|
|
#include <algorithm>
|
|
#include <memory>
|
|
|
|
#include "my_compiler.h"
|
|
#include "my_dbug.h"
|
|
#include "mysqld_error.h"
|
|
|
|
namespace keyring {
|
|
|
|
extern PSI_memory_key key_memory_KEYRING;
|
|
#ifdef HAVE_PSI_INTERFACE
|
|
PSI_file_key keyring_file_data_key;
|
|
PSI_file_key keyring_backup_file_data_key;
|
|
|
|
static PSI_file_info all_keyring_files[] = {
|
|
{&keyring_file_data_key, "keyring_file_data", 0, 0, PSI_DOCUMENT_ME},
|
|
{&keyring_backup_file_data_key, "keyring_backup_file_data", 0, 0,
|
|
PSI_DOCUMENT_ME}};
|
|
|
|
void keyring_init_psi_file_keys(void) {
|
|
const char *category = "keyring_file";
|
|
int count;
|
|
|
|
count = static_cast<int>(array_elements(all_keyring_files));
|
|
mysql_file_register(category, all_keyring_files, count);
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
constructor taking a single logger output and optional list of format versions
|
|
|
|
@param logger - extern logging object for tracing
|
|
@param versions - list of allowable file format versions
|
|
*/
|
|
Buffered_file_io::Buffered_file_io(ILogger *logger,
|
|
std::vector<std::string> const *versions)
|
|
: digest(SHA256, dummy_digest),
|
|
memory_needed_for_buffer(0),
|
|
file_version(keyring_file_version_2_0),
|
|
logger(logger),
|
|
file_io(logger),
|
|
file_arch(Converter::Arch::UNKNOWN),
|
|
native_arch(Converter::get_native_arch()) {
|
|
// by default we support only default keyring version
|
|
if (versions == nullptr)
|
|
checkers.push_back(checker_factory.getCheckerForVersion(file_version));
|
|
else
|
|
for (auto const &version : *versions) {
|
|
auto checker = checker_factory.getCheckerForVersion(version);
|
|
DBUG_ASSERT(checker != nullptr);
|
|
checkers.push_back(std::move(checker));
|
|
}
|
|
}
|
|
|
|
/**
|
|
builds backup file name on-the-fly
|
|
- shouldn't be used for write access
|
|
*/
|
|
std::string *Buffered_file_io::get_backup_filename() {
|
|
// do we have to "build" backup file name
|
|
if (unlikely(backup_filename.empty())) {
|
|
backup_filename.append(keyring_filename);
|
|
backup_filename.append(".backup");
|
|
}
|
|
|
|
return &backup_filename;
|
|
}
|
|
|
|
/**
|
|
tries opening backup file and passes file handle on success
|
|
|
|
@param backup_file - handle to opened backup file (output)
|
|
|
|
@return
|
|
@retval false - backup handle was successfully passed via parameter
|
|
@retval true - backup file could not be opened (probably missing)
|
|
*/
|
|
bool Buffered_file_io::open_backup_file(File *backup_file) {
|
|
// try opening backup file
|
|
*backup_file = file_io.open(keyring_backup_file_data_key,
|
|
get_backup_filename()->c_str(), O_RDONLY, MYF(0));
|
|
|
|
// most probably there was no backup file
|
|
return likely(*backup_file < 0);
|
|
}
|
|
|
|
/**
|
|
checks whether keyring file matches any known keyring file structure
|
|
|
|
@param file - file handle of keyring file
|
|
@param file_size - size of the keyring file
|
|
|
|
@return
|
|
@retval false - keyring file has valid structure
|
|
@retval true - keyring file does not have valid structure
|
|
*/
|
|
bool Buffered_file_io::check_file_structure(File file, size_t file_size) {
|
|
// check whether keyring file structure matches any of rules
|
|
for (auto &checker : checkers)
|
|
if (!checker->check_file_structure(file, file_size, &digest, &file_arch))
|
|
return false; // match is found
|
|
|
|
// no match was found, keyring file can't be used
|
|
logger->log(ERROR_LEVEL, ER_KEYRING_INCORRECT_FILE);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
loads keyring file content into a Buffer serialized object
|
|
- only called when keyring is initalizing
|
|
|
|
@param file - file handle of keyring file
|
|
@param buffer - serializable object to store file content to
|
|
|
|
@return
|
|
@retval false - file is loaded into buffer, or is empty
|
|
@retval true - an error has occurred during buffer loading
|
|
*/
|
|
bool Buffered_file_io::load_file_into_buffer(File file, Buffer *buffer) {
|
|
// position ourselves at file end, leave on failure
|
|
if (file_io.seek(file, 0, MY_SEEK_END, MYF(MY_WME)) == MY_FILEPOS_ERROR)
|
|
return true;
|
|
|
|
// get current file position (size of file)
|
|
my_off_t file_size = file_io.tell(file, MYF(MY_WME));
|
|
if (file_size == ((my_off_t)-1)) return true;
|
|
|
|
// we don't load if file's empty
|
|
if (file_size == 0) return false; // it is OK if file is empty
|
|
|
|
// file structure has to be valid, we also check the architecture
|
|
if (check_file_structure(file, file_size)) return true;
|
|
|
|
// calculate size required for buffer when wrappers are removed
|
|
const size_t digest_length = digest.is_empty ? 0 : SHA256_DIGEST_LENGTH;
|
|
size_t input_buffer_size =
|
|
file_size - Checker::EOF_TAG_SIZE - file_version.length() - digest_length;
|
|
|
|
// we have to be able to move ourselves beyond file version in file
|
|
if (file_io.seek(file, file_version.length(), MY_SEEK_SET, MYF(MY_WME)) ==
|
|
MY_FILEPOS_ERROR)
|
|
return true;
|
|
|
|
// we'll read if there's something to read
|
|
if (likely(input_buffer_size > 0)) {
|
|
// do we have file format mismatch
|
|
if (file_arch != native_arch) {
|
|
// load data to temp buffer
|
|
std::unique_ptr<uchar> tmp(new uchar[input_buffer_size]);
|
|
if (file_io.read(file, tmp.get(), input_buffer_size, MYF(MY_WME)) !=
|
|
input_buffer_size)
|
|
return true;
|
|
|
|
// convert data to appropriate format, leave on error
|
|
std::string converted;
|
|
if (Converter::convert_data(reinterpret_cast<const char *>(tmp.get()),
|
|
input_buffer_size, file_arch, native_arch,
|
|
converted))
|
|
return true;
|
|
|
|
// prepare buffer size and store converted data
|
|
buffer->reserve(converted.length());
|
|
memcpy(buffer->data, converted.c_str(), converted.length());
|
|
} else {
|
|
// buffer size has to be memory aligned to machine architecture (size_t)
|
|
if (input_buffer_size % sizeof(size_t) != 0) return true;
|
|
|
|
// reserve buffer memory and load directly to buffer
|
|
buffer->reserve(input_buffer_size);
|
|
if (file_io.read(file, buffer->data, input_buffer_size, MYF(MY_WME)) !=
|
|
input_buffer_size)
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// remember current buffer size
|
|
memory_needed_for_buffer = buffer->size;
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
restores keys from backup file if possible
|
|
- backup file has to exist and have a good structure
|
|
- backup file is always erased after the procedure
|
|
|
|
@return
|
|
@retval false - we successfully restored from backup, or no backup
|
|
@retval true - we encountered an error while trying to restore
|
|
*/
|
|
bool Buffered_file_io::recreate_keyring_from_backup_if_backup_exists() {
|
|
// try opening backup file
|
|
File backup_file;
|
|
if (open_backup_file(&backup_file))
|
|
return false; // if there's no backup file, that's not an error
|
|
|
|
// load keyring backup to a buffer
|
|
Buffer buffer;
|
|
if (load_file_into_buffer(backup_file, &buffer)) {
|
|
// backup file is malformed, delete it and emit warning
|
|
logger->log(WARNING_LEVEL, ER_KEYRING_FOUND_MALFORMED_BACKUP_FILE);
|
|
file_io.close(backup_file, MYF(0));
|
|
|
|
// remove invalid backup and leave, this is still not an error
|
|
return remove_backup(MYF(MY_WME));
|
|
}
|
|
|
|
// do not create keyring file from the backup if the backup file is empty
|
|
if (buffer.size == 0) {
|
|
logger->log(WARNING_LEVEL, ER_KEYRING_FAILED_TO_RESTORE_FROM_BACKUP_FILE);
|
|
remove_backup(MYF(MY_WME));
|
|
return false;
|
|
}
|
|
|
|
// try opening or creating main keyring file
|
|
File keyring_file =
|
|
file_io.open(keyring_file_data_key, keyring_filename.c_str(),
|
|
O_RDWR | O_CREAT, MYF(MY_WME));
|
|
|
|
// copy buffer with backup to main file, leave on any error
|
|
if (keyring_file < 0 || flush_buffer_to_storage(&buffer, keyring_file) ||
|
|
file_io.close(backup_file, MYF(MY_WME)) < 0 ||
|
|
file_io.close(keyring_file, MYF(MY_WME)) < 0)
|
|
|
|
{
|
|
// failure to do any of previous steps is an error
|
|
logger->log(ERROR_LEVEL, ER_KEYRING_FAILED_TO_RESTORE_FROM_BACKUP_FILE);
|
|
return true;
|
|
}
|
|
|
|
// we restored keyring from backup, so we can remove backup
|
|
return remove_backup(MYF(MY_WME));
|
|
}
|
|
|
|
/**
|
|
checks if keyring file exists, whether empty or not
|
|
- if keyring file is empty, it shall be deleted
|
|
|
|
@return
|
|
@retval false - keyring file can be opened for appending
|
|
@retval true - file I/O error (unreadable, missing...)
|
|
*/
|
|
bool Buffered_file_io::check_if_keyring_file_can_be_opened_or_created() {
|
|
// Check if the file exists
|
|
int file_exist = !my_access(keyring_filename.c_str(), F_OK);
|
|
|
|
// try creating file or opening existing
|
|
File file = file_io.open(
|
|
keyring_file_data_key, keyring_filename.c_str(),
|
|
file_exist && keyring_open_mode ? O_RDONLY : O_RDWR | O_CREAT,
|
|
MYF(MY_WME));
|
|
|
|
// if we can't open file or position ourselves at end - it's an error
|
|
if (file < 0 ||
|
|
file_io.seek(file, 0, MY_SEEK_END, MYF(MY_WME)) == MY_FILEPOS_ERROR)
|
|
return true;
|
|
|
|
// get local file position (i.e. file size), leave on error
|
|
my_off_t file_size = file_io.tell(file, MYF(MY_WME));
|
|
if (((file_size == (my_off_t)-1)) || file_io.close(file, MYF(MY_WME)) < 0)
|
|
return true;
|
|
|
|
// empty file (any remnant) should be deleted
|
|
if (file_size == 0 && file_io.remove(keyring_filename.c_str(), MYF(MY_WME)))
|
|
return true;
|
|
|
|
// keyring file is accessible (we can open or create it)
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
(override) prepares keyring file for usage
|
|
- initialization may include restoring keys from backup file, if available
|
|
- if no backup is available, keyring file is either opened or created
|
|
|
|
@param keyring_filename - file name of the keyring file to initialize
|
|
|
|
@return
|
|
@retval true - there was an error with initializing keyring file
|
|
@retval false - keyring file has been initialized successfully
|
|
*/
|
|
bool Buffered_file_io::init(std::string *keyring_filename) {
|
|
// file name can't be empty
|
|
DBUG_ASSERT(keyring_filename->empty() == false);
|
|
|
|
#ifdef HAVE_PSI_INTERFACE
|
|
keyring_init_psi_file_keys();
|
|
#endif
|
|
|
|
// set internal filename to provided name
|
|
this->keyring_filename = *keyring_filename;
|
|
|
|
// keys are restored from backup (optional, if available),
|
|
// after which we assure that keyring file is accessible
|
|
return recreate_keyring_from_backup_if_backup_exists() ||
|
|
check_if_keyring_file_can_be_opened_or_created();
|
|
}
|
|
|
|
/**
|
|
stores serialized keys from buffer, and accompanied data to file
|
|
|
|
@param buffer - object with serialized keys
|
|
@param buffer_digest - file digest sum
|
|
@param file - handle of file to store keys to
|
|
|
|
@return
|
|
@retval false - buffer content successfully copied to file
|
|
@retval true - errors occurred during file I/O
|
|
*/
|
|
bool Buffered_file_io::flush_buffer_to_file(Buffer *buffer,
|
|
Digest *buffer_digest, File file) {
|
|
const uchar *data = buffer->data;
|
|
size_t data_size = buffer->size;
|
|
std::string converted;
|
|
|
|
// check whether format conversion is required
|
|
if (native_arch != file_arch) {
|
|
if (Converter::convert_data(reinterpret_cast<char const *>(buffer->data),
|
|
buffer->size, native_arch, file_arch,
|
|
converted))
|
|
return true;
|
|
|
|
data = (const uchar *)converted.c_str();
|
|
data_size = converted.length();
|
|
}
|
|
|
|
// write file version, buffer content, EOF tag and digest sum to file
|
|
if (file_io.write(file, reinterpret_cast<const uchar *>(file_version.c_str()),
|
|
file_version.length(),
|
|
MYF(MY_WME)) == file_version.length() &&
|
|
file_io.write(file, data, data_size, MYF(MY_WME)) == data_size &&
|
|
file_io.write(
|
|
file, reinterpret_cast<const uchar *>(Checker::eofTAG.c_str()),
|
|
Checker::eofTAG.length(), MYF(MY_WME)) == Checker::eofTAG.length() &&
|
|
file_io.write(file, reinterpret_cast<const uchar *>(buffer_digest->value),
|
|
SHA256_DIGEST_LENGTH, MYF(0)) == SHA256_DIGEST_LENGTH)
|
|
return false;
|
|
|
|
// if we're here, some of upper operations failed
|
|
logger->log(ERROR_LEVEL, ER_KEYRING_FAILED_TO_FLUSH_KEYRING_TO_FILE);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
verifies that file structure of keyring file is valid
|
|
|
|
@param keyring_file - file handle of keyring file
|
|
|
|
@return
|
|
@retval false - keyring file structure is valid
|
|
@retval true - keyring file structure is invalid
|
|
*/
|
|
bool Buffered_file_io::check_keyring_file_structure(File keyring_file) {
|
|
// if file is missing - either we are currently creating new one, which is ok
|
|
// (digest should be set to dummy value), or it was deleted, which is not ok
|
|
if (keyring_file < 0)
|
|
return strncmp(reinterpret_cast<char *>(digest.value), dummy_digest,
|
|
SHA256_DIGEST_LENGTH) != 0;
|
|
|
|
// try positioning ourself at file end, leave on error
|
|
if (file_io.seek(keyring_file, 0, MY_SEEK_END, MYF(MY_WME)) ==
|
|
MY_FILEPOS_ERROR)
|
|
return true;
|
|
|
|
// determine current location (i.e. file size), leave on error
|
|
my_off_t file_size = file_io.tell(keyring_file, MYF(MY_WME));
|
|
if (file_size == ((my_off_t)-1)) return true;
|
|
|
|
// lets call all available rules to see if any checks out
|
|
return check_file_structure(keyring_file, file_size);
|
|
}
|
|
|
|
/**
|
|
(override) fetches serialized object and stores it to a backup file
|
|
- serialized object has to point to Buffer implementation
|
|
|
|
@param serialized_object - object holding keys to be backed-up
|
|
|
|
@return
|
|
@retval false - keys from serialized object were stored to backup
|
|
@retval true - error occurred during keys backup
|
|
*/
|
|
bool Buffered_file_io::flush_to_backup(ISerialized_object *serialized_object) {
|
|
// First open backup file then check keyring file. This way we make sure that
|
|
// media, where keyring file is written, is not replaced with some other media
|
|
// before backup file is written. In case media was changed backup file
|
|
// handler becomes invalid
|
|
File backup_file =
|
|
file_io.open(keyring_backup_file_data_key, get_backup_filename()->c_str(),
|
|
O_WRONLY | O_TRUNC | O_CREAT, MYF(MY_WME));
|
|
|
|
File keyring_file = file_io.open(keyring_file_data_key,
|
|
keyring_filename.c_str(), O_RDONLY, MYF(0));
|
|
|
|
// backup file must be available
|
|
if (backup_file < 0) {
|
|
// close the keyring file if necessary
|
|
if (keyring_file >= 0) file_io.close(keyring_file, MYF(MY_WME));
|
|
return true; // failure - no backup file
|
|
}
|
|
|
|
// try checking keyring file structure and closing it after that
|
|
if (check_keyring_file_structure(keyring_file) ||
|
|
(keyring_file >= 0 && file_io.close(keyring_file, MYF(MY_WME)) < 0)) {
|
|
// close keyring file if necessary
|
|
if (keyring_file >= 0) file_io.close(keyring_file, MYF(MY_WME));
|
|
|
|
// close backup and remove it
|
|
file_io.close(backup_file, MYF(MY_WME));
|
|
remove_backup(MYF(MY_WME));
|
|
|
|
return true; // failure - bad or missing keyring file
|
|
}
|
|
|
|
// upcast serialized object to buffer, verify result
|
|
Buffer *buffer = dynamic_cast<Buffer *>(serialized_object);
|
|
DBUG_ASSERT(buffer != NULL);
|
|
|
|
// calculate digest sum of buffer
|
|
Digest buffer_digest;
|
|
buffer_digest.compute(buffer->data, buffer->size);
|
|
|
|
// hook to crash the server before wiring the keyring backup file
|
|
DBUG_EXECUTE_IF("keyring_file_backup_fail", DBUG_SUICIDE(););
|
|
|
|
// store buffer to backup file and close it, leave on error
|
|
return buffer == NULL ||
|
|
flush_buffer_to_file(buffer, &buffer_digest, backup_file) ||
|
|
file_io.close(backup_file, MYF(MY_WME)) < 0;
|
|
}
|
|
|
|
/**
|
|
deletes backup file from file system
|
|
|
|
@param my_flags - flags to be used during file removal
|
|
|
|
@return
|
|
@retval false - backup file was successfully removed
|
|
@retval true - backup file removal failed
|
|
*/
|
|
bool Buffered_file_io::remove_backup(myf my_flags) {
|
|
return file_io.remove(get_backup_filename()->c_str(), my_flags);
|
|
}
|
|
|
|
/**
|
|
overwrites file with keys from buffer, and associated wrapper data
|
|
|
|
@param buffer - object with serialized key to store
|
|
@param file - handle of file to store keys to
|
|
|
|
@return
|
|
@retval false - buffer was successfully stored to a file
|
|
@retval true - there was an error during procedure
|
|
*/
|
|
bool Buffered_file_io::flush_buffer_to_storage(Buffer *buffer, File file) {
|
|
// clear file content and go to start of file, leave on error
|
|
if (file_io.truncate(file, MYF(MY_WME)) ||
|
|
file_io.seek(file, 0, MY_SEEK_SET, MYF(MY_WME)) != 0)
|
|
return true;
|
|
|
|
// calculate buffer digest
|
|
Digest buffer_digest;
|
|
buffer_digest.compute(buffer->data, buffer->size);
|
|
|
|
// store buffer to a file, leave on error
|
|
if (flush_buffer_to_file(buffer, &buffer_digest, file)) return true;
|
|
|
|
// remember current file digest
|
|
digest = buffer_digest;
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
(override) stores keys from serialized object to keyring file
|
|
- serialized object has to point to Buffer implementation
|
|
|
|
@param serialized_object - object holding keys to be stored
|
|
|
|
@return
|
|
@retval false - keys from serialized object were stored to keyring file
|
|
@retval true - error occurred during store operation
|
|
*/
|
|
bool Buffered_file_io::flush_to_storage(ISerialized_object *serialized_object) {
|
|
Buffer *buffer = dynamic_cast<Buffer *>(serialized_object);
|
|
DBUG_ASSERT(buffer != NULL);
|
|
DBUG_ASSERT(serialized_object->get_key_operation() != NONE);
|
|
|
|
// open keyring file
|
|
File keyring_file =
|
|
file_io.open(keyring_file_data_key, keyring_filename.c_str(),
|
|
O_CREAT | O_RDWR, MYF(MY_WME));
|
|
|
|
// we need valid keyring file, and writing has to succeed
|
|
if (keyring_file < 0 || check_keyring_file_structure(keyring_file) ||
|
|
flush_buffer_to_storage(buffer, keyring_file)) {
|
|
// close keyring file and return error
|
|
file_io.close(keyring_file, MYF(MY_WME));
|
|
return true;
|
|
}
|
|
|
|
// close file and remove backup, leave on error
|
|
if (file_io.close(keyring_file, MYF(MY_WME)) < 0 ||
|
|
remove_backup(MYF(MY_WME)))
|
|
return true;
|
|
|
|
// store information about memory required for keys in buffer
|
|
memory_needed_for_buffer = buffer->size;
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
(override) retrieves serialized which creates Buffer implementation from key
|
|
hash map
|
|
- used to prepare appropriate serialized object for class (Buffer)
|
|
|
|
@return - serializer which converts key hash map to a Buffer implementation
|
|
*/
|
|
ISerializer *Buffered_file_io::get_serializer() {
|
|
hash_to_buffer_serializer.set_memory_needed_for_buffer(
|
|
memory_needed_for_buffer);
|
|
return &hash_to_buffer_serializer;
|
|
}
|
|
|
|
/**
|
|
(override) retrieves serialized implementation of keys stored in keyring file
|
|
- keys object could be empty, if keyring file is empty
|
|
|
|
@param serialized_object - pointer-to-pointer of location to store keys
|
|
|
|
@return false - set of keys was successfully retrieved from keyring file
|
|
@return true - we were not able to retrieve serialized keys
|
|
*/
|
|
bool Buffered_file_io::get_serialized_object(
|
|
ISerialized_object **serialized_object) {
|
|
// Check if the file exists
|
|
int file_exist = !my_access(keyring_filename.c_str(), F_OK);
|
|
|
|
// try opening keyring file, leave on error
|
|
File file = file_io.open(
|
|
keyring_file_data_key, keyring_filename.c_str(),
|
|
file_exist && keyring_open_mode ? O_RDONLY : O_RDWR | O_CREAT,
|
|
MYF(MY_WME));
|
|
if (file < 0) return true;
|
|
|
|
// try loading file content into a Buffer implementation
|
|
std::unique_ptr<Buffer> buffer(new Buffer);
|
|
if (load_file_into_buffer(file, buffer.get())) {
|
|
// didn't work - close file and pass a null pointer
|
|
file_io.close(file, MYF(MY_WME));
|
|
*serialized_object = NULL;
|
|
return true;
|
|
}
|
|
|
|
// close keyring file, leave on error
|
|
if (file_io.close(file, MYF(MY_WME)) < 0) return true;
|
|
|
|
// if there were no keys in keyring file, we reset it
|
|
if (buffer->size == 0) buffer.reset(NULL);
|
|
|
|
// pass buffer memory to the caller
|
|
*serialized_object = buffer.release();
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
(override) verifies if there is more serialized objects in I/O object
|
|
|
|
@return
|
|
@retval true - there is at least one more serialized object to get
|
|
@retval false - there is no more serialized objects to get
|
|
*/
|
|
bool Buffered_file_io::has_next_serialized_object() {
|
|
// Buffered_file_io implementation uses a single serialized object
|
|
return false;
|
|
}
|
|
|
|
} // namespace keyring
|