mirror of
https://github.com/EvernodeXRPL/hpcore.git
synced 2026-04-29 15:37:59 +00:00
Code cleanup and naming consistency improvement. (#73)
This commit is contained in:
@@ -48,7 +48,7 @@ do
|
||||
node -p "JSON.stringify({...require('./tmp.json'), \
|
||||
binary: '/usr/local/bin/node', \
|
||||
binargs: './bin/contract.js', \
|
||||
appbill: 'appbill', \
|
||||
appbill: '', \
|
||||
appbillargs: '', \
|
||||
peerport: ${peerport}, \
|
||||
pubport: ${pubport}, \
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
App bill default implementation 1
|
||||
MSB of return value is reserved for appbill error
|
||||
bits 1-8 indicate whether or not public keys 0-6 passed appbill check
|
||||
|
||||
(to be completed)
|
||||
*/
|
||||
|
||||
@@ -577,10 +576,17 @@ int check_mode(int argc, char** argv, int print_balances) {
|
||||
return 128;
|
||||
}
|
||||
|
||||
if (correct_for_ed_keys(argc, argv, 2, 0))
|
||||
if (argc == 0 || argc % 2 == 1 && !print_balances) {
|
||||
fprintf(stderr, "appbill check mode requires a public key%s\n", (print_balances ? "" : " and an amount to check against"));
|
||||
return 128;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (correct_for_ed_keys(argc, argv, (print_balances ? 1 : 2), 0))
|
||||
return 128;
|
||||
|
||||
for (int i = 0; i < argc; i+=2) {
|
||||
for (int i = 0; i < argc; i+= ( print_balances ? 1 : 2 )) {
|
||||
// check the pubkey
|
||||
for (char* x = argv[i];; ++x) {
|
||||
if ( x - argv[i] == KEY_SIZE*2 && *x != '\0' ) {
|
||||
@@ -598,6 +604,9 @@ int check_mode(int argc, char** argv, int print_balances) {
|
||||
return 128;
|
||||
}
|
||||
|
||||
if (print_balances)
|
||||
continue;
|
||||
|
||||
// check the bytecount
|
||||
for (char* x = argv[i+1]; *x != '\0'; ++x) {
|
||||
if (*x >= '0' && *x <= '9')
|
||||
@@ -627,13 +636,14 @@ int check_mode(int argc, char** argv, int print_balances) {
|
||||
bits[i] = 0;
|
||||
|
||||
// loop keys, check balances
|
||||
for (int i = 0, j = 0; i < argc; i+=2, ++j) {
|
||||
for (int i = 0, j = 0; i < argc; i+=( print_balances ? 1 : 2 ), ++j) {
|
||||
// convert the argv from hex to binary
|
||||
uint8_t key[32];
|
||||
key_from_hex((uint8_t*)argv[i], key);
|
||||
|
||||
uint32_t bytecount = 0;
|
||||
sscanf(argv[i+1], "%d", &bytecount);
|
||||
if (!print_balances)
|
||||
sscanf(argv[i+1], "%d", &bytecount);
|
||||
|
||||
int error = 0;
|
||||
uint64_t balance = 0;
|
||||
@@ -702,4 +712,4 @@ int main(int argc, char** argv) {
|
||||
fprintf(stderr, "unknown mode, execution should not reach here\n");
|
||||
|
||||
return 128;
|
||||
}
|
||||
}
|
||||
@@ -82,7 +82,7 @@ int create_contract()
|
||||
boost::filesystem::create_directories(ctx.configdir);
|
||||
boost::filesystem::create_directories(ctx.histdir);
|
||||
boost::filesystem::create_directories(ctx.statedir);
|
||||
boost::filesystem::create_directories(ctx.statehistdir);
|
||||
boost::filesystem::create_directories(ctx.state_hist_dir);
|
||||
|
||||
//Create config file with default settings.
|
||||
|
||||
@@ -148,7 +148,7 @@ void set_contract_dir_paths(std::string exepath, std::string basedir)
|
||||
ctx.tlscertfile = ctx.configdir + "/tlscert.pem";
|
||||
ctx.histdir = basedir + "/hist";
|
||||
ctx.statedir = basedir + "/state";
|
||||
ctx.statehistdir = basedir + "/statehist";
|
||||
ctx.state_hist_dir = basedir + "/statehist";
|
||||
ctx.logdir = basedir + "/log";
|
||||
}
|
||||
|
||||
@@ -517,7 +517,7 @@ int validate_contract_dir_paths()
|
||||
ctx.configfile,
|
||||
ctx.histdir,
|
||||
ctx.statedir,
|
||||
ctx.statehistdir,
|
||||
ctx.state_hist_dir,
|
||||
ctx.tlskeyfile,
|
||||
ctx.tlscertfile};
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ struct contract_ctx
|
||||
std::string contractdir; // Contract base directory full path
|
||||
std::string histdir; // Contract history dir full path
|
||||
std::string statedir; // Contract executing state dir full path (This is the fuse mount point)
|
||||
std::string statehistdir; // Contract state history dir full path
|
||||
std::string state_hist_dir; // Contract state history dir full path
|
||||
std::string logdir; // Contract log dir full path
|
||||
std::string configdir; // Contract config dir full path
|
||||
std::string configfile; // Full path to the contract config file
|
||||
|
||||
@@ -170,6 +170,9 @@ void consensus()
|
||||
|
||||
if (should_request_history)
|
||||
{
|
||||
// TODO: If we are in a lcl fork condition try to rollback state with the help of
|
||||
// state_restore to rollback state checkpoints before requesting new state.
|
||||
|
||||
//handle minority going forward when boostrapping cluster.
|
||||
//Here we are mimicking invalid min ledger scenario.
|
||||
if (majority_lcl == GENESIS_LEDGER)
|
||||
@@ -410,7 +413,7 @@ bool verify_appbill_check(std::string_view pubkey, const size_t input_len)
|
||||
if (pid == 0)
|
||||
{
|
||||
// before execution chdir into a valid the latest state data directory that contains an appbill.table
|
||||
chdir(statefs::current_ctx.datadir.c_str());
|
||||
chdir(statefs::current_ctx.data_dir.c_str());
|
||||
int ret = execv(execv_args[0], execv_args);
|
||||
LOG_ERR << "Appbill process execv failed: " << ret;
|
||||
return false;
|
||||
|
||||
@@ -25,7 +25,14 @@ std::list<backlog_item> pending_requests;
|
||||
// List of submitted requests we are awaiting responses for, keyed by expected response hash.
|
||||
std::unordered_map<hasher::B2H, backlog_item, hasher::B2H_std_key_hasher> submitted_requests;
|
||||
|
||||
void request_state_from_peer(const std::string &path, const bool is_file, const std::string &lcl, const int32_t block_id, const hasher::B2H expected_hash)
|
||||
/**
|
||||
* Sends a state request to a random peer.
|
||||
* @param path Requested file or dir path.
|
||||
* @param is_file Whether the requested path if a file or dir.
|
||||
* @param block_id The requested block id. Only relevant if requesting a file block. Otherwise -1.
|
||||
* @param expected_hash The expected hash of the requested data. The peer will ignore the request if their hash is different.
|
||||
*/
|
||||
void request_state_from_peer(const std::string &path, const bool is_file, const int32_t block_id, const hasher::B2H expected_hash)
|
||||
{
|
||||
p2p::state_request sr;
|
||||
sr.parent_path = path;
|
||||
@@ -34,33 +41,40 @@ void request_state_from_peer(const std::string &path, const bool is_file, const
|
||||
sr.expected_hash = expected_hash;
|
||||
|
||||
p2p::peer_outbound_message msg(std::make_unique<flatbuffers::FlatBufferBuilder>(1024));
|
||||
fbschema::p2pmsg::create_msg_from_state_request(msg.builder(), sr, lcl);
|
||||
fbschema::p2pmsg::create_msg_from_state_request(msg.builder(), sr, ctx.lcl);
|
||||
p2p::send_message_to_random_peer(msg); //todo: send to a node that hold the majority state to improve reliability of retrieving state.
|
||||
}
|
||||
|
||||
/**
|
||||
* Creats the reply message for a given state request.
|
||||
* @param msg The peer outbound message reference to build up the reply message.
|
||||
* @param sr The state request which should be replied to.
|
||||
*/
|
||||
int create_state_response(p2p::peer_outbound_message &msg, const p2p::state_request &sr)
|
||||
{
|
||||
// If block_id > -1 this means this is a file block data request.
|
||||
if (sr.block_id > -1)
|
||||
{
|
||||
std::vector<uint8_t> blocks;
|
||||
|
||||
if (statefs::get_block(blocks, sr.parent_path, sr.block_id, sr.expected_hash) == -1)
|
||||
// Vector to hold the block bytes. Normally block size is constant BLOCK_SIZE (4MB), but the
|
||||
// last block of a file may have a smaller size.
|
||||
std::vector<uint8_t> block;
|
||||
if (statefs::get_block(block, sr.parent_path, sr.block_id, sr.expected_hash) == -1)
|
||||
return -1;
|
||||
|
||||
p2p::block_response resp;
|
||||
resp.path = sr.parent_path;
|
||||
resp.block_id = sr.block_id;
|
||||
resp.hash = sr.expected_hash;
|
||||
resp.data = std::string_view(reinterpret_cast<const char *>(blocks.data()), blocks.size());
|
||||
resp.data = std::string_view(reinterpret_cast<const char *>(block.data()), block.size());
|
||||
|
||||
fbschema::p2pmsg::create_msg_from_block_response(msg.builder(), resp, ctx.lcl);
|
||||
}
|
||||
else
|
||||
{
|
||||
// File state request means we have to reply with the file block hash map.
|
||||
if (sr.is_file)
|
||||
{
|
||||
std::vector<uint8_t> existing_block_hashmap;
|
||||
|
||||
if (statefs::get_block_hash_map(existing_block_hashmap, sr.parent_path, sr.expected_hash) == -1)
|
||||
return -1;
|
||||
|
||||
@@ -68,8 +82,8 @@ int create_state_response(p2p::peer_outbound_message &msg, const p2p::state_requ
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the state request is for a directory we need to reply with the file system entries and their hashes inside that dir.
|
||||
std::unordered_map<std::string, p2p::state_fs_hash_entry> existing_fs_entries;
|
||||
|
||||
if (statefs::get_fs_entry_hashes(existing_fs_entries, sr.parent_path, sr.expected_hash) == -1)
|
||||
return -1;
|
||||
|
||||
@@ -80,6 +94,11 @@ int create_state_response(p2p::peer_outbound_message &msg, const p2p::state_requ
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates state sync process by setting up context variables and sending the initial state request.
|
||||
* @param state_hash_to_request Peer's expected state hash. If peer doesn't have this as its state hash the
|
||||
* request will be ignord.
|
||||
*/
|
||||
void start_state_sync(const hasher::B2H state_hash_to_request)
|
||||
{
|
||||
{
|
||||
@@ -98,13 +117,16 @@ void start_state_sync(const hasher::B2H state_hash_to_request)
|
||||
submit_request(backlog_item{BACKLOG_ITEM_TYPE::DIR, "/", -1, state_hash_to_request});
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the state sync loop.
|
||||
*/
|
||||
int run_state_sync_iterator()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
util::sleep(SYNC_LOOP_WAIT);
|
||||
|
||||
// TODO: Also bypass peer session handler responses if not syncing.
|
||||
// TODO: Also bypass peer session handler state responses if we're not syncing.
|
||||
if (!ctx.is_state_syncing)
|
||||
continue;
|
||||
|
||||
@@ -188,16 +210,22 @@ int run_state_sync_iterator()
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits a pending state request to the peer.
|
||||
*/
|
||||
void submit_request(const backlog_item &request)
|
||||
{
|
||||
LOG_DBG << "Submitting state request. type:" << request.type << " path:" << request.path << " blockid:" << request.block_id;
|
||||
LOG_DBG << "Submitting state request. type:" << request.type << " path:" << request.path << " block_id:" << request.block_id;
|
||||
|
||||
submitted_requests.try_emplace(request.expected_hash, request);
|
||||
|
||||
const bool is_file = request.type != BACKLOG_ITEM_TYPE::DIR;
|
||||
request_state_from_peer(request.path, is_file, ctx.lcl, request.block_id, request.expected_hash);
|
||||
request_state_from_peer(request.path, is_file, request.block_id, request.expected_hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process state file system entry response for a directory.
|
||||
*/
|
||||
int handle_fs_entry_response(const fbschema::p2pmsg::Fs_Entry_Response *fs_entry_resp)
|
||||
{
|
||||
std::unordered_map<std::string, p2p::state_fs_hash_entry> state_fs_entry_list;
|
||||
|
||||
@@ -33,7 +33,7 @@ extern std::list<std::string> candidate_state_responses;
|
||||
|
||||
int create_state_response(p2p::peer_outbound_message &msg, const p2p::state_request &sr);
|
||||
|
||||
void request_state_from_peer(const std::string &path, const bool is_file, const std::string &lcl, const int32_t block_id, const hasher::B2H expected_hash);
|
||||
void request_state_from_peer(const std::string &path, const bool is_file, const int32_t block_id, const hasher::B2H expected_hash);
|
||||
|
||||
void start_state_sync(const hasher::B2H state_hash_to_request);
|
||||
|
||||
|
||||
@@ -192,7 +192,7 @@ int main(int argc, char **argv)
|
||||
LOG_INFO << "Operating mode: "
|
||||
<< (conf::cfg.mode == conf::OPERATING_MODE::OBSERVING ? "Observing" : "Proposing");
|
||||
|
||||
statefs::init(conf::ctx.statehistdir);
|
||||
statefs::init(conf::ctx.state_hist_dir);
|
||||
|
||||
if (p2p::init() != 0 || usr::init() != 0 || cons::init() != 0)
|
||||
return -1;
|
||||
|
||||
@@ -59,26 +59,30 @@ struct npl_message
|
||||
std::string data;
|
||||
};
|
||||
|
||||
|
||||
// Represents a state request sent to a peer.
|
||||
struct state_request
|
||||
{
|
||||
std::string parent_path;
|
||||
bool is_file;
|
||||
int32_t block_id;
|
||||
hasher::B2H expected_hash;
|
||||
std::string parent_path; // The requested file or dir path.
|
||||
bool is_file; // Whether the path is a file or dir.
|
||||
int32_t block_id; // Block id of the file if we are requesting for file block. Otherwise -1.
|
||||
hasher::B2H expected_hash; // The expected hash of the requested result.
|
||||
};
|
||||
|
||||
// Represents state file system entry.
|
||||
struct state_fs_hash_entry
|
||||
{
|
||||
bool is_file;
|
||||
hasher::B2H hash;
|
||||
bool is_file; // Whether this is a file or dir.
|
||||
hasher::B2H hash; // Hash of the file or dir.
|
||||
};
|
||||
|
||||
// Represents a file block data resposne.
|
||||
struct block_response
|
||||
{
|
||||
std::string path;
|
||||
uint32_t block_id;
|
||||
std::string_view data;
|
||||
hasher::B2H hash;
|
||||
std::string path; // Path of the file.
|
||||
uint32_t block_id; // Id of the block where the data belongs to.
|
||||
std::string_view data; // The block data.
|
||||
hasher::B2H hash; // Hash of the bloc data.
|
||||
};
|
||||
|
||||
struct message_collection
|
||||
|
||||
@@ -182,7 +182,7 @@ int start_state_monitor()
|
||||
// Fill process args.
|
||||
char *execv_args[4];
|
||||
execv_args[0] = conf::ctx.statemonexepath.data();
|
||||
execv_args[1] = conf::ctx.statehistdir.data();
|
||||
execv_args[1] = conf::ctx.state_hist_dir.data();
|
||||
execv_args[2] = conf::ctx.statedir.data();
|
||||
execv_args[3] = NULL;
|
||||
|
||||
@@ -215,8 +215,8 @@ int stop_state_monitor()
|
||||
|
||||
// Update the hash tree.
|
||||
hasher::B2H statehash = hasher::B2H_empty;
|
||||
statefs::hashtree_builder htreebuilder(statefs::get_statedir_context());
|
||||
if (htreebuilder.generate(statehash) != 0)
|
||||
statefs::hashtree_builder htree_builder(statefs::get_state_dir_context());
|
||||
if (htree_builder.generate(statehash) != 0)
|
||||
return -1;
|
||||
|
||||
cons::ctx.curr_hash_state = std::string(reinterpret_cast<const char *>(&statehash), hasher::HASH_SIZE);
|
||||
|
||||
@@ -7,11 +7,23 @@
|
||||
namespace statefs
|
||||
{
|
||||
|
||||
hashmap_builder::hashmap_builder(const statedir_context &ctx) : ctx(ctx)
|
||||
/**
|
||||
* Hashmap builder class is responsible for updating file hash based on the modified blocks of a file.
|
||||
*/
|
||||
|
||||
hashmap_builder::hashmap_builder(const state_dir_context &ctx) : ctx(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
int hashmap_builder::generate_hashmap_forfile(hasher::B2H &parentdirhash, const std::string &filepath, const std::string filerelpath, const std::map<uint32_t, hasher::B2H> &changedblocks)
|
||||
/**
|
||||
* Generates/updates the block hash map for a file and updates the parent dir hash accordingly as well.
|
||||
* @param parent_dir_hash Hash of the parent directory. This will be updated of the file hash was updated.
|
||||
* @param filepath Full path to the actual state file.
|
||||
* @param file_relpath The relative path to the state file from the state data directory.
|
||||
* @param changed_blocks Index of changed blocks and the new hashes to be used as a hint.
|
||||
* @return 0 on success. -1 on failure.
|
||||
*/
|
||||
int hashmap_builder::generate_hashmap_for_file(hasher::B2H &parent_dir_hash, const std::string &filepath, const std::string &file_relpath, const std::map<uint32_t, hasher::B2H> &changed_blocks)
|
||||
{
|
||||
// We attempt to avoid a full rebuild of the block hash map file when possible.
|
||||
// For this optimisation, both the block hash map (.bhmap) file and the
|
||||
@@ -30,39 +42,39 @@ int hashmap_builder::generate_hashmap_forfile(hasher::B2H &parentdirhash, const
|
||||
LOG_ERR << errno << ": Open failed " << filepath;
|
||||
return -1;
|
||||
}
|
||||
const off_t filelength = lseek(orifd, 0, SEEK_END);
|
||||
uint32_t blockcount = ceil((double)filelength / (double)BLOCK_SIZE);
|
||||
const off_t file_length = lseek(orifd, 0, SEEK_END);
|
||||
const uint32_t block_count = ceil((double)file_length / (double)BLOCK_SIZE);
|
||||
|
||||
// Attempt to read the existing block hash map file.
|
||||
std::string bhmapfile;
|
||||
std::vector<char> bhmapdata;
|
||||
if (read_blockhashmap(bhmapdata, bhmapfile, filerelpath) == -1)
|
||||
std::string bhmap_file;
|
||||
std::vector<char> bhmap_data;
|
||||
if (read_block_hashmap(bhmap_data, bhmap_file, file_relpath) == -1)
|
||||
{
|
||||
close(orifd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
hasher::B2H oldfilehash = hasher::B2H_empty;
|
||||
if (!bhmapdata.empty())
|
||||
memcpy(&oldfilehash, bhmapdata.data(), hasher::HASH_SIZE);
|
||||
hasher::B2H old_file_hash = hasher::B2H_empty;
|
||||
if (!bhmap_data.empty())
|
||||
memcpy(&old_file_hash, bhmap_data.data(), hasher::HASH_SIZE);
|
||||
|
||||
// Array to contain the updated block hashes. Slot 0 is for the root hash.
|
||||
// Allocating hash array on the heap to avoid filling limited stack space.
|
||||
std::unique_ptr<hasher::B2H[]> hashes = std::make_unique<hasher::B2H[]>(1 + blockcount);
|
||||
const size_t hashes_size = (1 + blockcount) * hasher::HASH_SIZE;
|
||||
std::unique_ptr<hasher::B2H[]> hashes = std::make_unique<hasher::B2H[]>(1 + block_count);
|
||||
const size_t hashes_size = (1 + block_count) * hasher::HASH_SIZE;
|
||||
|
||||
if (changedblocks.empty())
|
||||
if (changed_blocks.empty())
|
||||
{
|
||||
// Attempt to read the delta block index file.
|
||||
std::map<uint32_t, hasher::B2H> bindex;
|
||||
uint32_t original_blockcount;
|
||||
if (get_blockindex(bindex, original_blockcount, filerelpath) == -1)
|
||||
uint32_t original_block_count;
|
||||
if (get_delta_block_index(bindex, original_block_count, file_relpath) == -1)
|
||||
{
|
||||
close(orifd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (update_hashes_with_backup_blockhints(hashes.get(), hashes_size, filerelpath, orifd, blockcount, original_blockcount, bindex, bhmapdata) == -1)
|
||||
if (update_hashes_with_backup_block_hints(hashes.get(), hashes_size, file_relpath, orifd, block_count, original_block_count, bindex, bhmap_data) == -1)
|
||||
{
|
||||
close(orifd);
|
||||
return -1;
|
||||
@@ -70,7 +82,7 @@ int hashmap_builder::generate_hashmap_forfile(hasher::B2H &parentdirhash, const
|
||||
}
|
||||
else
|
||||
{
|
||||
if (update_hashes_with_changed_blockhints(hashes.get(), hashes_size, filerelpath, orifd, blockcount, changedblocks, bhmapdata) == -1)
|
||||
if (update_hashes_with_changed_block_hints(hashes.get(), hashes_size, file_relpath, orifd, block_count, changed_blocks, bhmap_data) == -1)
|
||||
{
|
||||
close(orifd);
|
||||
return -1;
|
||||
@@ -79,35 +91,42 @@ int hashmap_builder::generate_hashmap_forfile(hasher::B2H &parentdirhash, const
|
||||
|
||||
close(orifd);
|
||||
|
||||
if (write_blockhashmap(bhmapfile, hashes.get(), hashes_size) == -1)
|
||||
if (write_block_hashmap(bhmap_file, hashes.get(), hashes_size) == -1)
|
||||
return -1;
|
||||
|
||||
if (update_hashtree_entry(parentdirhash, !bhmapdata.empty(), oldfilehash, hashes[0], bhmapfile, filerelpath) == -1)
|
||||
if (update_hashtree_entry(parent_dir_hash, !bhmap_data.empty(), old_file_hash, hashes[0], bhmap_file, file_relpath) == -1)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hashmap_builder::read_blockhashmap(std::vector<char> &bhmapdata, std::string &bhmapfile, const std::string &relpath)
|
||||
/**
|
||||
* Reads the block hash map of a given data file into the provided vector.
|
||||
* @param bhmap_data Vector to copy the block hash map contents.
|
||||
* @param bhmap_file The full path to the block hash map file pointed to by the relative path.
|
||||
* @param relpath The relative path of the actual data file.
|
||||
* @return 0 on success. -1 on failure.
|
||||
*/
|
||||
int hashmap_builder::read_block_hashmap(std::vector<char> &bhmap_data, std::string &bhmap_file, const std::string &relpath)
|
||||
{
|
||||
bhmapfile.reserve(ctx.blockhashmapdir.length() + relpath.length() + HASHMAP_EXT_LEN);
|
||||
bhmapfile.append(ctx.blockhashmapdir).append(relpath).append(HASHMAP_EXT);
|
||||
bhmap_file.reserve(ctx.block_hashmap_dir.length() + relpath.length() + BLOCK_HASHMAP_EXT_LEN);
|
||||
bhmap_file.append(ctx.block_hashmap_dir).append(relpath).append(BLOCK_HASHMAP_EXT);
|
||||
|
||||
if (boost::filesystem::exists(bhmapfile))
|
||||
if (boost::filesystem::exists(bhmap_file))
|
||||
{
|
||||
int hmapfd = open(bhmapfile.c_str(), O_RDONLY);
|
||||
int hmapfd = open(bhmap_file.c_str(), O_RDONLY);
|
||||
if (hmapfd == -1)
|
||||
{
|
||||
LOG_ERR << errno << ": Open failed " << bhmapfile;
|
||||
LOG_ERR << errno << ": Open failed " << bhmap_file;
|
||||
return -1;
|
||||
}
|
||||
|
||||
off_t size = lseek(hmapfd, 0, SEEK_END);
|
||||
bhmapdata.resize(size);
|
||||
bhmap_data.resize(size);
|
||||
|
||||
if (pread(hmapfd, bhmapdata.data(), size, 0) == -1)
|
||||
if (pread(hmapfd, bhmap_data.data(), size, 0) == -1)
|
||||
{
|
||||
LOG_ERR << errno << ": Read failed " << bhmapfile;
|
||||
LOG_ERR << errno << ": Read failed " << bhmap_file;
|
||||
close(hmapfd);
|
||||
return -1;
|
||||
}
|
||||
@@ -117,7 +136,7 @@ int hashmap_builder::read_blockhashmap(std::vector<char> &bhmapdata, std::string
|
||||
else
|
||||
{
|
||||
// Create directory tree if not exist so we are able to create the hashmap files.
|
||||
boost::filesystem::path hmapsubdir = boost::filesystem::path(bhmapfile).parent_path();
|
||||
boost::filesystem::path hmapsubdir = boost::filesystem::path(bhmap_file).parent_path();
|
||||
if (created_bhmapsubdirs.count(hmapsubdir.string()) == 0)
|
||||
{
|
||||
boost::filesystem::create_directories(hmapsubdir);
|
||||
@@ -128,99 +147,118 @@ int hashmap_builder::read_blockhashmap(std::vector<char> &bhmapdata, std::string
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hashmap_builder::get_blockindex(std::map<uint32_t, hasher::B2H> &idxmap, uint32_t &totalblockcount, const std::string &filerelpath)
|
||||
/**
|
||||
* Reads the delta block index of a file.
|
||||
* @param idxmap Map to copy the block index contents (block id --> hash).
|
||||
* @param total_block_count Reference to hold the total block count of the original data file.
|
||||
* @param file_relpath Relative path to the data file.
|
||||
* @return 0 on success. -1 on failure.
|
||||
*/
|
||||
int hashmap_builder::get_delta_block_index(std::map<uint32_t, hasher::B2H> &idxmap, uint32_t &total_block_count, const std::string &file_relpath)
|
||||
{
|
||||
std::string bindexfile;
|
||||
bindexfile.reserve(ctx.deltadir.length() + filerelpath.length() + BLOCKINDEX_EXT_LEN);
|
||||
bindexfile.append(ctx.deltadir).append(filerelpath).append(BLOCKINDEX_EXT);
|
||||
std::string bindex_file;
|
||||
bindex_file.reserve(ctx.delta_dir.length() + file_relpath.length() + BLOCK_INDEX_EXT_LEN);
|
||||
bindex_file.append(ctx.delta_dir).append(file_relpath).append(BLOCK_INDEX_EXT);
|
||||
|
||||
if (boost::filesystem::exists(bindexfile))
|
||||
if (boost::filesystem::exists(bindex_file))
|
||||
{
|
||||
std::ifstream infile(bindexfile, std::ios::binary | std::ios::ate);
|
||||
std::streamsize idxsize = infile.tellg();
|
||||
infile.seekg(0, std::ios::beg);
|
||||
std::ifstream in_file(bindex_file, std::ios::binary | std::ios::ate);
|
||||
std::streamsize idx_size = in_file.tellg();
|
||||
in_file.seekg(0, std::ios::beg);
|
||||
|
||||
// Read the block index file into a vector.
|
||||
std::vector<char> bindex(idxsize);
|
||||
if (infile.read(bindex.data(), idxsize))
|
||||
std::vector<char> bindex(idx_size);
|
||||
if (in_file.read(bindex.data(), idx_size))
|
||||
{
|
||||
// First 8 bytes contain the original file length.
|
||||
off_t orifilelen;
|
||||
memcpy(&orifilelen, bindex.data(), 8);
|
||||
totalblockcount = ceil((double)orifilelen / (double)BLOCK_SIZE);
|
||||
total_block_count = ceil((double)orifilelen / (double)BLOCK_SIZE);
|
||||
|
||||
// Skip the first 8 bytes and loop through index entries.
|
||||
for (uint32_t idxoffset = 8; idxoffset < bindex.size();)
|
||||
for (uint32_t idx_offset = 8; idx_offset < bindex.size();)
|
||||
{
|
||||
// Read the block no. (4 bytes) of where this block is from in the original file.
|
||||
uint32_t blockno = 0;
|
||||
memcpy(&blockno, bindex.data() + idxoffset, 4);
|
||||
idxoffset += 12; // Skip the cached block offset (8 bytes)
|
||||
uint32_t block_no = 0;
|
||||
memcpy(&block_no, bindex.data() + idx_offset, 4);
|
||||
idx_offset += 12; // Skip the cached block offset (8 bytes)
|
||||
|
||||
// Read the block hash (32 bytes).
|
||||
hasher::B2H hash;
|
||||
memcpy(&hash, bindex.data() + idxoffset, 32);
|
||||
idxoffset += 32;
|
||||
memcpy(&hash, bindex.data() + idx_offset, 32);
|
||||
idx_offset += 32;
|
||||
|
||||
idxmap.try_emplace(blockno, hash);
|
||||
idxmap.try_emplace(block_no, hash);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERR << errno << ": Read failed " << bindexfile;
|
||||
LOG_ERR << errno << ": Read failed " << bindex_file;
|
||||
return -1;
|
||||
}
|
||||
|
||||
infile.close();
|
||||
in_file.close();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hashmap_builder::update_hashes_with_backup_blockhints(
|
||||
hasher::B2H *hashes, const off_t hashes_size, const std::string &relpath, const int orifd,
|
||||
const uint32_t blockcount, const uint32_t original_blockcount, const std::map<uint32_t, hasher::B2H> &bindex, const std::vector<char> &bhmapdata)
|
||||
/**
|
||||
* Updates the hash map with the use of delta backup block ids.
|
||||
* @param hashes Pointer to the hash array to copy the block hashes after the update.
|
||||
* @param hashes_size Byte length of the hashes array.
|
||||
* @param relpath Relative path of the data file.
|
||||
* @param orifd An open file descriptor to the data file.
|
||||
* @param block_count Block count of the updated file.
|
||||
* @param original_block_count Original block count before the update.
|
||||
* @param bindex Delta backup block index map.
|
||||
* @param bhmap_data Contents of the existing block hash map.
|
||||
* @return 0 on success. -1 on failure.
|
||||
*/
|
||||
int hashmap_builder::update_hashes_with_backup_block_hints(
|
||||
hasher::B2H *hashes, const off_t hashes_size, const std::string &relpath, const int orifd, const uint32_t block_count,
|
||||
const uint32_t original_block_count, const std::map<uint32_t, hasher::B2H> &bindex, const std::vector<char> &bhmap_data)
|
||||
{
|
||||
uint32_t nohint_blockstart = 0;
|
||||
|
||||
// If both existing delta block index and block hash map is available, we can just overlay the
|
||||
// changed block hashes (mentioned in the delta block index) on top of the old block hashes.
|
||||
// This would prevent unncessarily hashing lot of blocks.
|
||||
if (!bhmapdata.empty() && !bindex.empty())
|
||||
if (!bhmap_data.empty() && !bindex.empty())
|
||||
{
|
||||
// Load old hashes.
|
||||
memcpy(hashes, bhmapdata.data(), hashes_size < bhmapdata.size() ? hashes_size : bhmapdata.size());
|
||||
memcpy(hashes, bhmap_data.data(), hashes_size < bhmap_data.size() ? hashes_size : bhmap_data.size());
|
||||
|
||||
// Refer to the block index and rehash the changed blocks.
|
||||
for (const auto [blockid, oldhash] : bindex)
|
||||
for (const auto [block_id, old_hash] : bindex)
|
||||
{
|
||||
// If the blockid from the block index is no longer there, that means the current file is
|
||||
// If the block_id from the block index is no longer there, that means the current file is
|
||||
// shorter than the previous version. So we can stop hashing at this point.
|
||||
if (blockid >= blockcount)
|
||||
if (block_id >= block_count)
|
||||
break;
|
||||
|
||||
if (compute_blockhash(hashes[blockid + 1], blockid, orifd, relpath) == -1)
|
||||
if (compute_blockhash(hashes[block_id + 1], block_id, orifd, relpath) == -1)
|
||||
return -1;
|
||||
}
|
||||
|
||||
// If the current file has more blocks than the previous version, we need to hash those
|
||||
// additional blocks as well.
|
||||
if (blockcount > original_blockcount)
|
||||
nohint_blockstart = original_blockcount;
|
||||
if (block_count > original_block_count)
|
||||
nohint_blockstart = original_block_count;
|
||||
else
|
||||
nohint_blockstart = blockcount; // No additional blocks remaining.
|
||||
nohint_blockstart = block_count; // No additional blocks remaining.
|
||||
}
|
||||
|
||||
//Hash any additional blocks that has to be hashed without the guidance of block index.
|
||||
for (uint32_t blockid = nohint_blockstart; blockid < blockcount; blockid++)
|
||||
for (uint32_t block_id = nohint_blockstart; block_id < block_count; block_id++)
|
||||
{
|
||||
if (compute_blockhash(hashes[blockid + 1], blockid, orifd, relpath) == -1)
|
||||
if (compute_blockhash(hashes[block_id + 1], block_id, orifd, relpath) == -1)
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Calculate the new file hash: filehash = HASH(filename + XOR(block hashes))
|
||||
hasher::B2H filehash = hasher::B2H_empty;
|
||||
for (int i = 1; i <= blockcount; i++)
|
||||
for (int i = 1; i <= block_count; i++)
|
||||
filehash ^= hashes[i];
|
||||
|
||||
// Rehash the file hash with filename included.
|
||||
@@ -231,9 +269,20 @@ int hashmap_builder::update_hashes_with_backup_blockhints(
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hashmap_builder::update_hashes_with_changed_blockhints(
|
||||
/**
|
||||
* Updates the hash map with the use of list of updated block ids.
|
||||
* @param hashes Pointer to the hash array to copy the block hashes after the update.
|
||||
* @param hashes_size Byte length of the hashes array.
|
||||
* @param relpath Relative path of the data file.
|
||||
* @param orifd An open file descriptor to the data file.
|
||||
* @param block_count Block count of the updated file.
|
||||
* @param bindex Map of updated block ids and new hashes.
|
||||
* @param bhmap_data Contents of the existing block hash map.
|
||||
* @return 0 on success. -1 on failure.
|
||||
*/
|
||||
int hashmap_builder::update_hashes_with_changed_block_hints(
|
||||
hasher::B2H *hashes, const off_t hashes_size, const std::string &relpath, const int orifd,
|
||||
const uint32_t blockcount, const std::map<uint32_t, hasher::B2H> &bindex, const std::vector<char> &bhmapdata)
|
||||
const uint32_t block_count, const std::map<uint32_t, hasher::B2H> &bindex, const std::vector<char> &bhmap_data)
|
||||
{
|
||||
// If both existing delta block index and block hash map is available, we can just overlay the
|
||||
// changed block hashes (mentioned in the delta block index) on top of the old block hashes.
|
||||
@@ -242,19 +291,19 @@ int hashmap_builder::update_hashes_with_changed_blockhints(
|
||||
if (!bindex.empty())
|
||||
{
|
||||
// Load old hashes if exists.
|
||||
if (!bhmapdata.empty())
|
||||
memcpy(hashes, bhmapdata.data(), hashes_size < bhmapdata.size() ? hashes_size : bhmapdata.size());
|
||||
if (!bhmap_data.empty())
|
||||
memcpy(hashes, bhmap_data.data(), hashes_size < bhmap_data.size() ? hashes_size : bhmap_data.size());
|
||||
|
||||
// Refer to the block index and overlay the new hash into the hashes array.
|
||||
for (const auto [blockid, newhash] : bindex)
|
||||
hashes[blockid + 1] = newhash;
|
||||
for (const auto [block_id, new_hash] : bindex)
|
||||
hashes[block_id + 1] = new_hash;
|
||||
|
||||
// If the block hash map didn't existed, we need to calculate and fill the unchanged block hashes from the actual file.
|
||||
if (bhmapdata.empty())
|
||||
if (bhmap_data.empty())
|
||||
{
|
||||
for (uint32_t blockid = 0; blockid < blockcount; blockid++)
|
||||
for (uint32_t block_id = 0; block_id < block_count; block_id++)
|
||||
{
|
||||
if (bindex.count(blockid) == 0 && compute_blockhash(hashes[blockid + 1], blockid, orifd, relpath) == -1)
|
||||
if (bindex.count(block_id) == 0 && compute_blockhash(hashes[block_id + 1], block_id, orifd, relpath) == -1)
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -262,16 +311,16 @@ int hashmap_builder::update_hashes_with_changed_blockhints(
|
||||
else
|
||||
{
|
||||
// If we don't have the changed block index, we have to hash the entire file blocks again.
|
||||
for (uint32_t blockid = 0; blockid < blockcount; blockid++)
|
||||
for (uint32_t block_id = 0; block_id < block_count; block_id++)
|
||||
{
|
||||
if (compute_blockhash(hashes[blockid + 1], blockid, orifd, relpath) == -1)
|
||||
if (compute_blockhash(hashes[block_id + 1], block_id, orifd, relpath) == -1)
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the new file hash: filehash = HASH(filename + XOR(block hashes))
|
||||
hasher::B2H filehash = hasher::B2H_empty;
|
||||
for (int i = 1; i <= blockcount; i++)
|
||||
for (int i = 1; i <= block_count; i++)
|
||||
filehash ^= hashes[i];
|
||||
|
||||
// Rehash the file hash with filename included.
|
||||
@@ -282,42 +331,57 @@ int hashmap_builder::update_hashes_with_changed_blockhints(
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hashmap_builder::compute_blockhash(hasher::B2H &hash, uint32_t blockid, int filefd, const std::string &relpath)
|
||||
/**
|
||||
* Calculates the hash of the specified block id of a file.
|
||||
* @param hash Reference to assign the calculated hash.
|
||||
* @param block_id Id of the block to be hashed.
|
||||
* @param filefd Open file descriptor for the state data file.
|
||||
* @param relpath Relative path of the state data file.
|
||||
* @return 0 on success. -1 on failure.
|
||||
*/
|
||||
int hashmap_builder::compute_blockhash(hasher::B2H &hash, const uint32_t block_id, const int filefd, const std::string &relpath)
|
||||
{
|
||||
// Allocating block buffer on the heap to avoid filling limited stack space.
|
||||
std::unique_ptr<char[]> blockbuf = std::make_unique<char[]>(BLOCK_SIZE);
|
||||
const off_t blockoffset = BLOCK_SIZE * blockid;
|
||||
size_t bytesread = pread(filefd, blockbuf.get(), BLOCK_SIZE, blockoffset);
|
||||
if (bytesread == -1)
|
||||
std::unique_ptr<char[]> block_buf = std::make_unique<char[]>(BLOCK_SIZE);
|
||||
const off_t block_offset = BLOCK_SIZE * block_id;
|
||||
size_t bytes_read = pread(filefd, block_buf.get(), BLOCK_SIZE, block_offset);
|
||||
if (bytes_read == -1)
|
||||
{
|
||||
LOG_ERR << errno << ": Read failed " << relpath;
|
||||
return -1;
|
||||
}
|
||||
|
||||
hash = hasher::hash(&blockoffset, 8, blockbuf.get(), bytesread);
|
||||
hash = hasher::hash(&block_offset, 8, block_buf.get(), bytes_read);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hashmap_builder::write_blockhashmap(const std::string &bhmapfile, const hasher::B2H *hashes, const off_t hashes_size)
|
||||
/**
|
||||
* Saves the block hash map into the relevant .bhmap file.
|
||||
* @param bhmap_file Full path to the block hash map file.
|
||||
* @param hashes Pointer to the hashes array containing the root hash and block hashes.
|
||||
* @param hashes_size Byte length of the hashes array.
|
||||
* @return 0 on success. -1 on failure.
|
||||
*/
|
||||
int hashmap_builder::write_block_hashmap(const std::string &bhmap_file, const hasher::B2H *hashes, const off_t hashes_size)
|
||||
{
|
||||
int hmapfd = open(bhmapfile.c_str(), O_RDWR | O_TRUNC | O_CREAT, FILE_PERMS);
|
||||
int hmapfd = open(bhmap_file.c_str(), O_RDWR | O_TRUNC | O_CREAT, FILE_PERMS);
|
||||
if (hmapfd == -1)
|
||||
{
|
||||
LOG_ERR << errno << ": Open failed " << bhmapfile;
|
||||
LOG_ERR << errno << ": Open failed " << bhmap_file;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Write the updated hash list into the block hash map file.
|
||||
if (pwrite(hmapfd, hashes, hashes_size, 0) == -1)
|
||||
{
|
||||
LOG_ERR << errno << ": Write failed " << bhmapfile;
|
||||
LOG_ERR << errno << ": Write failed " << bhmap_file;
|
||||
close(hmapfd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ftruncate(hmapfd, hashes_size) == -1)
|
||||
{
|
||||
LOG_ERR << errno << ": Truncate failed " << bhmapfile;
|
||||
LOG_ERR << errno << ": Truncate failed " << bhmap_file;
|
||||
close(hmapfd);
|
||||
return -1;
|
||||
}
|
||||
@@ -325,91 +389,113 @@ int hashmap_builder::write_blockhashmap(const std::string &bhmapfile, const hash
|
||||
close(hmapfd);
|
||||
}
|
||||
|
||||
int hashmap_builder::update_hashtree_entry(hasher::B2H &parentdirhash, const bool oldbhmap_exists, const hasher::B2H oldfilehash, const hasher::B2H newfilehash, const std::string &bhmapfile, const std::string &relpath)
|
||||
/**
|
||||
* Updates a file hash and adjust parent dir hash of the hash tree.
|
||||
* @param parent_dir_hash Current hash of the parent dir. This will be assigned the new hash after the update.
|
||||
* @param old_bhmap_exists Whether the block hash map file already exists or not.
|
||||
* @param old_file_hash Old file hash. (0000 if this is a new file)
|
||||
* @param new_file_hash New file hash.
|
||||
* @param bhmap_file Full path to the block hash map file.
|
||||
* @param relpath Relative path to the state data file.
|
||||
* @return 0 on success. -1 on failure.
|
||||
*/
|
||||
int hashmap_builder::update_hashtree_entry(hasher::B2H &parent_dir_hash, const bool old_bhmap_exists, const hasher::B2H old_file_hash,
|
||||
const hasher::B2H new_file_hash, const std::string &bhmap_file, const std::string &relpath)
|
||||
{
|
||||
std::string hardlinkdir(ctx.hashtreedir);
|
||||
const std::string relpathdir = boost::filesystem::path(relpath).parent_path().string();
|
||||
std::string hardlink_dir(ctx.hashtree_dir);
|
||||
const std::string relpath_dir = boost::filesystem::path(relpath).parent_path().string();
|
||||
|
||||
hardlinkdir.append(relpathdir);
|
||||
if (relpathdir != "/")
|
||||
hardlinkdir.append("/");
|
||||
hardlink_dir.append(relpath_dir);
|
||||
if (relpath_dir != "/")
|
||||
hardlink_dir.append("/");
|
||||
|
||||
std::stringstream newhlpath;
|
||||
newhlpath << hardlinkdir << newfilehash << ".rh";
|
||||
std::stringstream new_hl_path;
|
||||
new_hl_path << hardlink_dir << new_file_hash << ".rh";
|
||||
|
||||
if (oldbhmap_exists)
|
||||
// TODO: Even though we maintain hardlinks named after the file hash, we don't actually utilize them elsewhere.
|
||||
// The intention is to be able to get a hash listing of the entire directory. Such ability is useful to serve state
|
||||
// requests. However since state requests need the file name along with the hash we have to resort to iterating each
|
||||
// .bhmap file and reading the file hash from first 32 bytes.
|
||||
|
||||
if (old_bhmap_exists)
|
||||
{
|
||||
// Rename the existing hard link if old block hash map existed.
|
||||
// We thereby assume the old hard link also existed.
|
||||
std::stringstream oldhlpath;
|
||||
oldhlpath << hardlinkdir << oldfilehash << ".rh";
|
||||
if (rename(oldhlpath.str().c_str(), newhlpath.str().c_str()) == -1)
|
||||
oldhlpath << hardlink_dir << old_file_hash << ".rh";
|
||||
if (rename(oldhlpath.str().c_str(), new_hl_path.str().c_str()) == -1)
|
||||
return -1;
|
||||
|
||||
// Subtract the old root hash and add the new root hash from the parent dir hash.
|
||||
parentdirhash ^= oldfilehash;
|
||||
parentdirhash ^= newfilehash;
|
||||
parent_dir_hash ^= old_file_hash;
|
||||
parent_dir_hash ^= new_file_hash;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a new hard link with new root hash as the name.
|
||||
if (link(bhmapfile.c_str(), newhlpath.str().c_str()) == -1)
|
||||
if (link(bhmap_file.c_str(), new_hl_path.str().c_str()) == -1)
|
||||
return -1;
|
||||
|
||||
// Add the new root hash to parent hash.
|
||||
parentdirhash ^= newfilehash;
|
||||
parent_dir_hash ^= new_file_hash;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hashmap_builder::remove_hashmapfile(hasher::B2H &parentdirhash, const std::string &bhmapfile)
|
||||
/**
|
||||
* Removes an existing block hash map file. Caled when deleting a state data file.
|
||||
* @param parent_dir_hash Current hash of the parent dir. This will be assigned the new hash after the update.
|
||||
* @param Full path to the block hash map file.
|
||||
* @return 0 on success. -1 on failure.
|
||||
*/
|
||||
int hashmap_builder::remove_hashmap_file(hasher::B2H &parent_dir_hash, const std::string &bhmap_file)
|
||||
{
|
||||
if (boost::filesystem::exists(bhmapfile))
|
||||
if (boost::filesystem::exists(bhmap_file))
|
||||
{
|
||||
int hmapfd = open(bhmapfile.data(), O_RDONLY);
|
||||
int hmapfd = open(bhmap_file.data(), O_RDONLY);
|
||||
if (hmapfd == -1)
|
||||
{
|
||||
LOG_ERR << errno << ": Open failed " << bhmapfile;
|
||||
LOG_ERR << errno << ": Open failed " << bhmap_file;
|
||||
return -1;
|
||||
}
|
||||
|
||||
hasher::B2H filehash;
|
||||
if (read(hmapfd, &filehash, hasher::HASH_SIZE) == -1)
|
||||
{
|
||||
LOG_ERR << errno << ": Read failed " << bhmapfile;
|
||||
LOG_ERR << errno << ": Read failed " << bhmap_file;
|
||||
close(hmapfd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Delete the .bhmap file.
|
||||
if (remove(bhmapfile.c_str()) == -1)
|
||||
if (remove(bhmap_file.c_str()) == -1)
|
||||
{
|
||||
LOG_ERR << errno << ": Delete failed " << bhmapfile;
|
||||
LOG_ERR << errno << ": Delete failed " << bhmap_file;
|
||||
close(hmapfd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Delete the hardlink of the .bhmap file.
|
||||
std::string hardlinkdir(ctx.hashtreedir);
|
||||
const std::string relpath = get_relpath(bhmapfile, ctx.blockhashmapdir);
|
||||
const std::string relpathdir = boost::filesystem::path(relpath).parent_path().string();
|
||||
std::string hardlink_dir(ctx.hashtree_dir);
|
||||
const std::string relpath = get_relpath(bhmap_file, ctx.block_hashmap_dir);
|
||||
const std::string relpath_dir = boost::filesystem::path(relpath).parent_path().string();
|
||||
|
||||
hardlinkdir.append(relpathdir);
|
||||
if (relpathdir != "/")
|
||||
hardlinkdir.append("/");
|
||||
hardlink_dir.append(relpath_dir);
|
||||
if (relpath_dir != "/")
|
||||
hardlink_dir.append("/");
|
||||
|
||||
std::stringstream hlpath;
|
||||
hlpath << hardlinkdir << filehash << ".rh";
|
||||
hlpath << hardlink_dir << filehash << ".rh";
|
||||
if (remove(hlpath.str().c_str()) == -1)
|
||||
{
|
||||
LOG_ERR << errno << ": Delete failed for halrd link " << filehash << " of " << bhmapfile;
|
||||
LOG_ERR << errno << ": Delete failed for hard link " << filehash << " of " << bhmap_file;
|
||||
close(hmapfd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// XOR parent dir hash with file hash so the file hash gets removed from parent dir hash.
|
||||
parentdirhash ^= filehash;
|
||||
parent_dir_hash ^= filehash;
|
||||
close(hmapfd);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,26 +11,26 @@ namespace statefs
|
||||
class hashmap_builder
|
||||
{
|
||||
private:
|
||||
const statedir_context ctx;
|
||||
const state_dir_context ctx;
|
||||
// List of new block hash map sub directories created during the session.
|
||||
std::unordered_set<std::string> created_bhmapsubdirs;
|
||||
|
||||
int read_blockhashmap(std::vector<char> &bhmapdata, std::string &hmapfile, const std::string &relpath);
|
||||
int get_blockindex(std::map<uint32_t, hasher::B2H> &idxmap, uint32_t &totalblockcount, const std::string &filerelpath);
|
||||
int update_hashes_with_backup_blockhints(
|
||||
int read_block_hashmap(std::vector<char> &bhmap_data, std::string &hmapfile, const std::string &relpath);
|
||||
int get_delta_block_index(std::map<uint32_t, hasher::B2H> &idxmap, uint32_t &total_block_count, const std::string &file_relpath);
|
||||
int update_hashes_with_backup_block_hints(
|
||||
hasher::B2H *hashes, const off_t hashes_size, const std::string &relpath, const int orifd,
|
||||
const uint32_t blockcount, const uint32_t original_blockcount, const std::map<uint32_t, hasher::B2H> &bindex, const std::vector<char> &bhmapdata);
|
||||
int update_hashes_with_changed_blockhints(
|
||||
const uint32_t block_count, const uint32_t original_block_count, const std::map<uint32_t, hasher::B2H> &bindex, const std::vector<char> &bhmap_data);
|
||||
int update_hashes_with_changed_block_hints(
|
||||
hasher::B2H *hashes, const off_t hashes_size, const std::string &relpath, const int orifd,
|
||||
const uint32_t blockcount, const std::map<uint32_t, hasher::B2H> &bindex, const std::vector<char> &bhmapdata);
|
||||
int compute_blockhash(hasher::B2H &hash, uint32_t blockid, int filefd, const std::string &relpath);
|
||||
int write_blockhashmap(const std::string &bhmapfile, const hasher::B2H *hashes, const off_t hashes_size);
|
||||
int update_hashtree_entry(hasher::B2H &parentdirhash, const bool oldbhmap_exists, const hasher::B2H oldfilehash, const hasher::B2H newfilehash, const std::string &bhmapfile, const std::string &relpath);
|
||||
const uint32_t block_count, const std::map<uint32_t, hasher::B2H> &bindex, const std::vector<char> &bhmap_data);
|
||||
int compute_blockhash(hasher::B2H &hash, const uint32_t block_id, const int filefd, const std::string &relpath);
|
||||
int write_block_hashmap(const std::string &bhmap_file, const hasher::B2H *hashes, const off_t hashes_size);
|
||||
int update_hashtree_entry(hasher::B2H &parent_dir_hash, const bool old_bhmap_exists, const hasher::B2H old_file_hash, const hasher::B2H new_file_hash, const std::string &bhmap_file, const std::string &relpath);
|
||||
|
||||
public:
|
||||
hashmap_builder(const statedir_context &ctx);
|
||||
int generate_hashmap_forfile(hasher::B2H &parentdirhash, const std::string &filepath, const std::string filerelpath, const std::map<uint32_t, hasher::B2H> &changedblocks);
|
||||
int remove_hashmapfile(hasher::B2H &parentdirhash, const std::string &filepath);
|
||||
hashmap_builder(const state_dir_context &ctx);
|
||||
int generate_hashmap_for_file(hasher::B2H &parent_dir_hash, const std::string &filepath, const std::string &file_relpath, const std::map<uint32_t, hasher::B2H> &changed_blocks);
|
||||
int remove_hashmap_file(hasher::B2H &parent_dir_hash, const std::string &filepath);
|
||||
};
|
||||
|
||||
} // namespace statefs
|
||||
|
||||
@@ -6,187 +6,187 @@
|
||||
namespace statefs
|
||||
{
|
||||
|
||||
hashtree_builder::hashtree_builder(const statedir_context &ctx) : ctx(ctx), hmapbuilder(ctx)
|
||||
hashtree_builder::hashtree_builder(const state_dir_context &ctx) : ctx(ctx), hmapbuilder(ctx)
|
||||
{
|
||||
force_rebuild_all = false;
|
||||
hintmode = false;
|
||||
hint_mode = false;
|
||||
}
|
||||
|
||||
int hashtree_builder::generate(hasher::B2H &roothash)
|
||||
int hashtree_builder::generate(hasher::B2H &root_hash)
|
||||
{
|
||||
// Load modified file path hints if available.
|
||||
populate_hintpaths_from_idxfile(IDX_TOUCHEDFILES);
|
||||
populate_hintpaths_from_idxfile(IDX_NEWFILES);
|
||||
hintmode = !hintpaths.empty();
|
||||
populate_hint_paths_from_idx_file(IDX_TOUCHED_FILES);
|
||||
populate_hint_paths_from_idx_file(IDX_NEW_FILES);
|
||||
hint_mode = !hint_paths.empty();
|
||||
|
||||
return traverse_and_generate(roothash);
|
||||
return traverse_and_generate(root_hash);
|
||||
}
|
||||
|
||||
int hashtree_builder::generate(hasher::B2H &roothash, const bool force_all)
|
||||
int hashtree_builder::generate(hasher::B2H &root_hash, const bool force_all)
|
||||
{
|
||||
force_rebuild_all = force_all;
|
||||
if (force_rebuild_all)
|
||||
{
|
||||
boost::filesystem::remove_all(ctx.blockhashmapdir);
|
||||
boost::filesystem::remove_all(ctx.hashtreedir);
|
||||
boost::filesystem::remove_all(ctx.block_hashmap_dir);
|
||||
boost::filesystem::remove_all(ctx.hashtree_dir);
|
||||
|
||||
boost::filesystem::create_directories(ctx.blockhashmapdir);
|
||||
boost::filesystem::create_directories(ctx.hashtreedir);
|
||||
boost::filesystem::create_directories(ctx.block_hashmap_dir);
|
||||
boost::filesystem::create_directories(ctx.hashtree_dir);
|
||||
}
|
||||
|
||||
return traverse_and_generate(roothash);
|
||||
return traverse_and_generate(root_hash);
|
||||
}
|
||||
|
||||
int hashtree_builder::generate(hasher::B2H &roothash, const std::unordered_map<std::string, std::map<uint32_t, hasher::B2H>> &touchedfiles)
|
||||
int hashtree_builder::generate(hasher::B2H &root_hash, const std::unordered_map<std::string, std::map<uint32_t, hasher::B2H>> &touched_files)
|
||||
{
|
||||
hintmode = true;
|
||||
fileblockindex = touchedfiles;
|
||||
for (const auto &[relpath, bindex] : touchedfiles)
|
||||
insert_hintpath(relpath);
|
||||
hint_mode = true;
|
||||
file_block_index = touched_files;
|
||||
for (const auto &[relpath, bindex] : touched_files)
|
||||
insert_hint_path(relpath);
|
||||
|
||||
return traverse_and_generate(roothash);
|
||||
return traverse_and_generate(root_hash);
|
||||
}
|
||||
|
||||
int hashtree_builder::traverse_and_generate(hasher::B2H &roothash)
|
||||
int hashtree_builder::traverse_and_generate(hasher::B2H &root_hash)
|
||||
{
|
||||
// Load current root hash if exist.
|
||||
const std::string dirhashfile = ctx.hashtreedir + "/" + DIRHASH_FNAME;
|
||||
roothash = get_existingdirhash(dirhashfile);
|
||||
const std::string dir_hash_file = ctx.hashtree_dir + "/" + DIR_HASH_FNAME;
|
||||
root_hash = get_existing_dir_hash(dir_hash_file);
|
||||
|
||||
traversel_rootdir = ctx.datadir;
|
||||
traversel_rootdir = ctx.data_dir;
|
||||
removal_mode = false;
|
||||
if (update_hashtree(roothash) != 0)
|
||||
if (update_hashtree(root_hash) != 0)
|
||||
return -1;
|
||||
|
||||
// If there are any remaining hint files directly under this directory, that means
|
||||
// those files are no longer there. So we need to delete the corresponding .bhmap and rh files
|
||||
// and adjust the directory hash accordingly.
|
||||
if (hintmode && !hintpaths.empty())
|
||||
if (hint_mode && !hint_paths.empty())
|
||||
{
|
||||
traversel_rootdir = ctx.blockhashmapdir;
|
||||
traversel_rootdir = ctx.block_hashmap_dir;
|
||||
removal_mode = true;
|
||||
if (update_hashtree(roothash) != 0)
|
||||
if (update_hashtree(root_hash) != 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hashtree_builder::update_hashtree(hasher::B2H &roothash)
|
||||
int hashtree_builder::update_hashtree(hasher::B2H &root_hash)
|
||||
{
|
||||
hintpath_map::iterator hintdir_itr = hintpaths.end();
|
||||
if (!should_process_dir(hintdir_itr, traversel_rootdir))
|
||||
hintpath_map::iterator hint_dir_itr = hint_paths.end();
|
||||
if (!should_process_dir(hint_dir_itr, traversel_rootdir))
|
||||
return 0;
|
||||
|
||||
if (update_hashtree_fordir(roothash, traversel_rootdir, hintdir_itr, true) != 0)
|
||||
if (update_hashtree_fordir(root_hash, traversel_rootdir, hint_dir_itr, true) != 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hashtree_builder::update_hashtree_fordir(hasher::B2H &parentdirhash, const std::string &dirpath, const hintpath_map::iterator hintdir_itr, const bool isrootlevel)
|
||||
int hashtree_builder::update_hashtree_fordir(hasher::B2H &parent_dir_hash, const std::string &dirpath, const hintpath_map::iterator hint_dir_itr, const bool is_root_level)
|
||||
{
|
||||
const std::string htreedirpath = switch_basepath(dirpath, traversel_rootdir, ctx.hashtreedir);
|
||||
const std::string htree_dirpath = switch_base_path(dirpath, traversel_rootdir, ctx.hashtree_dir);
|
||||
|
||||
// Load current dir hash if exist.
|
||||
const std::string dirhashfile = htreedirpath + "/" + DIRHASH_FNAME;
|
||||
hasher::B2H dirhash = get_existingdirhash(dirhashfile);
|
||||
const std::string dir_hash_file = htree_dirpath + "/" + DIR_HASH_FNAME;
|
||||
hasher::B2H dir_hash = get_existing_dir_hash(dir_hash_file);
|
||||
|
||||
// Remember the dir hash before we mutate it.
|
||||
hasher::B2H original_dirhash = dirhash;
|
||||
hasher::B2H original_dir_hash = dir_hash;
|
||||
|
||||
// Iterate files/subdirs inside this dir.
|
||||
const boost::filesystem::directory_iterator itrend;
|
||||
for (boost::filesystem::directory_iterator itr(dirpath); itr != itrend; itr++)
|
||||
const boost::filesystem::directory_iterator itr_end;
|
||||
for (boost::filesystem::directory_iterator itr(dirpath); itr != itr_end; itr++)
|
||||
{
|
||||
const bool isdir = boost::filesystem::is_directory(itr->path());
|
||||
const std::string pathstr = itr->path().string();
|
||||
const bool is_dir = boost::filesystem::is_directory(itr->path());
|
||||
const std::string path_str = itr->path().string();
|
||||
|
||||
if (isdir)
|
||||
if (is_dir)
|
||||
{
|
||||
hintpath_map::iterator hintsubdir_itr = hintpaths.end();
|
||||
if (!should_process_dir(hintsubdir_itr, pathstr))
|
||||
hintpath_map::iterator hint_subdir_itr = hint_paths.end();
|
||||
if (!should_process_dir(hint_subdir_itr, path_str))
|
||||
continue;
|
||||
|
||||
if (update_hashtree_fordir(dirhash, pathstr, hintsubdir_itr, false) != 0)
|
||||
if (update_hashtree_fordir(dir_hash, path_str, hint_subdir_itr, false) != 0)
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!should_process_file(hintdir_itr, pathstr))
|
||||
if (!should_process_file(hint_dir_itr, path_str))
|
||||
continue;
|
||||
|
||||
if (process_file(dirhash, pathstr, htreedirpath) != 0)
|
||||
if (process_file(dir_hash, path_str, htree_dirpath) != 0)
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// If there are no more files in the hint dir, delete the hint dir entry as well.
|
||||
if (hintdir_itr != hintpaths.end() && hintdir_itr->second.empty())
|
||||
hintpaths.erase(hintdir_itr);
|
||||
if (hint_dir_itr != hint_paths.end() && hint_dir_itr->second.empty())
|
||||
hint_paths.erase(hint_dir_itr);
|
||||
|
||||
// In removalmode, we check whether the dir is empty. If so we remove the dir as well.
|
||||
if (removal_mode && boost::filesystem::is_empty(dirpath))
|
||||
{
|
||||
// We remove the dirs if we are below root level only.
|
||||
// Otherwise we only remove root dir.hash file.
|
||||
if (!isrootlevel)
|
||||
if (!is_root_level)
|
||||
{
|
||||
boost::filesystem::remove_all(dirpath);
|
||||
boost::filesystem::remove_all(htreedirpath);
|
||||
boost::filesystem::remove_all(htree_dirpath);
|
||||
}
|
||||
else
|
||||
{
|
||||
boost::filesystem::remove(dirhashfile);
|
||||
boost::filesystem::remove(dir_hash_file);
|
||||
}
|
||||
|
||||
// Subtract the original dir hash from the parent dir hash.
|
||||
parentdirhash ^= original_dirhash;
|
||||
parent_dir_hash ^= original_dir_hash;
|
||||
}
|
||||
else if (dirhash != original_dirhash)
|
||||
else if (dir_hash != original_dir_hash)
|
||||
{
|
||||
// If dir hash has changed, write it back to dir hash file.
|
||||
if (save_dirhash(dirhashfile, dirhash) == -1)
|
||||
if (save_dir_hash(dir_hash_file, dir_hash) == -1)
|
||||
return -1;
|
||||
|
||||
// Also update the parent dir hash by subtracting the old hash and adding the new hash.
|
||||
parentdirhash ^= original_dirhash;
|
||||
parentdirhash ^= dirhash;
|
||||
parent_dir_hash ^= original_dir_hash;
|
||||
parent_dir_hash ^= dir_hash;
|
||||
}
|
||||
else
|
||||
{
|
||||
parentdirhash = dirhash;
|
||||
parent_dir_hash = dir_hash;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
hasher::B2H hashtree_builder::get_existingdirhash(const std::string &dirhashfile)
|
||||
hasher::B2H hashtree_builder::get_existing_dir_hash(const std::string &dir_hash_file)
|
||||
{
|
||||
// Load current dir hash if exist.
|
||||
hasher::B2H dirhash = hasher::B2H_empty;
|
||||
int dirhashfd = open(dirhashfile.c_str(), O_RDONLY);
|
||||
if (dirhashfd > 0)
|
||||
hasher::B2H dir_hash = hasher::B2H_empty;
|
||||
int dir_hash_fd = open(dir_hash_file.c_str(), O_RDONLY);
|
||||
if (dir_hash_fd > 0)
|
||||
{
|
||||
read(dirhashfd, &dirhash, hasher::HASH_SIZE);
|
||||
close(dirhashfd);
|
||||
read(dir_hash_fd, &dir_hash, hasher::HASH_SIZE);
|
||||
close(dir_hash_fd);
|
||||
}
|
||||
return dirhash;
|
||||
return dir_hash;
|
||||
}
|
||||
|
||||
int hashtree_builder::save_dirhash(const std::string &dirhashfile, hasher::B2H dirhash)
|
||||
int hashtree_builder::save_dir_hash(const std::string &dir_hash_file, hasher::B2H dir_hash)
|
||||
{
|
||||
int dirhashfd = open(dirhashfile.c_str(), O_RDWR | O_TRUNC | O_CREAT, FILE_PERMS);
|
||||
if (dirhashfd == -1)
|
||||
int dir_hash_fd = open(dir_hash_file.c_str(), O_RDWR | O_TRUNC | O_CREAT, FILE_PERMS);
|
||||
if (dir_hash_fd == -1)
|
||||
return -1;
|
||||
|
||||
if (write(dirhashfd, &dirhash, hasher::HASH_SIZE) == -1)
|
||||
if (write(dir_hash_fd, &dir_hash, hasher::HASH_SIZE) == -1)
|
||||
{
|
||||
close(dirhashfd);
|
||||
close(dir_hash_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
close(dirhashfd);
|
||||
close(dir_hash_fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -195,17 +195,17 @@ inline bool hashtree_builder::should_process_dir(hintpath_map::iterator &dir_itr
|
||||
if (force_rebuild_all)
|
||||
return true;
|
||||
|
||||
return (hintmode ? get_hinteddir_match(dir_itr, dirpath) : true);
|
||||
return (hint_mode ? get_hinteddir_match(dir_itr, dirpath) : true);
|
||||
}
|
||||
|
||||
bool hashtree_builder::should_process_file(const hintpath_map::iterator hintdir_itr, const std::string filepath)
|
||||
bool hashtree_builder::should_process_file(const hintpath_map::iterator hint_dir_itr, const std::string filepath)
|
||||
{
|
||||
if (force_rebuild_all)
|
||||
return true;
|
||||
|
||||
if (hintmode)
|
||||
if (hint_mode)
|
||||
{
|
||||
if (hintdir_itr == hintpaths.end())
|
||||
if (hint_dir_itr == hint_paths.end())
|
||||
return false;
|
||||
|
||||
std::string relpath = get_relpath(filepath, traversel_rootdir);
|
||||
@@ -213,82 +213,82 @@ bool hashtree_builder::should_process_file(const hintpath_map::iterator hintdir_
|
||||
// If in removal mode, we are traversing .bhmap files. Hence we should truncate .bhmap extension
|
||||
// before we search for the path in file hints.
|
||||
if (removal_mode)
|
||||
relpath = relpath.substr(0, relpath.length() - HASHMAP_EXT_LEN);
|
||||
relpath = relpath.substr(0, relpath.length() - BLOCK_HASHMAP_EXT_LEN);
|
||||
|
||||
std::unordered_set<std::string> &hintfiles = hintdir_itr->second;
|
||||
const auto hintfile_itr = hintfiles.find(relpath);
|
||||
if (hintfile_itr == hintfiles.end())
|
||||
std::unordered_set<std::string> &hint_files = hint_dir_itr->second;
|
||||
const auto hint_file_itr = hint_files.find(relpath);
|
||||
if (hint_file_itr == hint_files.end())
|
||||
return false;
|
||||
|
||||
// Erase the visiting filepath from hint files.
|
||||
hintfiles.erase(hintfile_itr);
|
||||
hint_files.erase(hint_file_itr);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int hashtree_builder::process_file(hasher::B2H &parentdirhash, const std::string &filepath, const std::string &htreedirpath)
|
||||
int hashtree_builder::process_file(hasher::B2H &parent_dir_hash, const std::string &filepath, const std::string &htree_dirpath)
|
||||
{
|
||||
if (!removal_mode)
|
||||
{
|
||||
// Create directory tree if not exist so we are able to create the file root hash files (hard links).
|
||||
if (created_htreesubdirs.count(htreedirpath) == 0)
|
||||
if (created_htree_subdirs.count(htree_dirpath) == 0)
|
||||
{
|
||||
boost::filesystem::create_directories(htreedirpath);
|
||||
created_htreesubdirs.emplace(htreedirpath);
|
||||
boost::filesystem::create_directories(htree_dirpath);
|
||||
created_htree_subdirs.emplace(htree_dirpath);
|
||||
}
|
||||
|
||||
std::string relpath = get_relpath(filepath, ctx.datadir);
|
||||
std::map<uint32_t, hasher::B2H> changedblocks = fileblockindex[relpath];
|
||||
const std::string relpath = get_relpath(filepath, ctx.data_dir);
|
||||
const std::map<uint32_t, hasher::B2H> &changed_blocks = file_block_index[relpath];
|
||||
|
||||
if (hmapbuilder.generate_hashmap_forfile(parentdirhash, filepath, relpath, changedblocks) == -1)
|
||||
if (hmapbuilder.generate_hashmap_for_file(parent_dir_hash, filepath, relpath, changed_blocks) == -1)
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hmapbuilder.remove_hashmapfile(parentdirhash, filepath) == -1)
|
||||
if (hmapbuilder.remove_hashmap_file(parent_dir_hash, filepath) == -1)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void hashtree_builder::populate_hintpaths_from_idxfile(const char *const idxfile)
|
||||
void hashtree_builder::populate_hint_paths_from_idx_file(const char *const idxfile)
|
||||
{
|
||||
std::ifstream infile(std::string(ctx.deltadir).append(idxfile));
|
||||
if (!infile.fail())
|
||||
std::ifstream in_file(std::string(ctx.delta_dir).append(idxfile));
|
||||
if (!in_file.fail())
|
||||
{
|
||||
for (std::string relpath; std::getline(infile, relpath);)
|
||||
insert_hintpath(relpath);
|
||||
infile.close();
|
||||
for (std::string relpath; std::getline(in_file, relpath);)
|
||||
insert_hint_path(relpath);
|
||||
in_file.close();
|
||||
}
|
||||
}
|
||||
|
||||
void hashtree_builder::insert_hintpath(const std::string &relpath)
|
||||
void hashtree_builder::insert_hint_path(const std::string &relpath)
|
||||
{
|
||||
boost::filesystem::path p_relpath(relpath);
|
||||
std::string parentdir = p_relpath.parent_path().string();
|
||||
hintpaths[parentdir].emplace(relpath);
|
||||
std::string parent_dir = p_relpath.parent_path().string();
|
||||
hint_paths[parent_dir].emplace(relpath);
|
||||
}
|
||||
|
||||
bool hashtree_builder::get_hinteddir_match(hintpath_map::iterator &matchitr, const std::string &dirpath)
|
||||
bool hashtree_builder::get_hinteddir_match(hintpath_map::iterator &match_itr, const std::string &dirpath)
|
||||
{
|
||||
// First check whether there's an exact match. If not check for a partial match.
|
||||
// Exact match will return the iterator. Partial match or not found will return end() iterator.
|
||||
const std::string relpath = get_relpath(dirpath, traversel_rootdir);
|
||||
const auto exactmatchitr = hintpaths.find(relpath);
|
||||
const auto exact_match_itr = hint_paths.find(relpath);
|
||||
|
||||
if (exactmatchitr != hintpaths.end())
|
||||
if (exact_match_itr != hint_paths.end())
|
||||
{
|
||||
matchitr = exactmatchitr;
|
||||
match_itr = exact_match_itr;
|
||||
return true;
|
||||
}
|
||||
|
||||
for (auto itr = hintpaths.begin(); itr != hintpaths.end(); itr++)
|
||||
for (auto itr = hint_paths.begin(); itr != hint_paths.end(); itr++)
|
||||
{
|
||||
if (strncmp(relpath.c_str(), itr->first.c_str(), relpath.length()) == 0)
|
||||
{
|
||||
// Partial match found.
|
||||
matchitr = hintpaths.end();
|
||||
match_itr = hint_paths.end();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,39 +14,39 @@ typedef std::unordered_map<std::string, std::unordered_set<std::string>> hintpat
|
||||
class hashtree_builder
|
||||
{
|
||||
private:
|
||||
const statedir_context ctx;
|
||||
const state_dir_context ctx;
|
||||
hashmap_builder hmapbuilder;
|
||||
|
||||
// Hint path map with parent dir as key and list of file paths under each parent dir.
|
||||
hintpath_map hintpaths;
|
||||
hintpath_map hint_paths;
|
||||
bool force_rebuild_all;
|
||||
bool hintmode;
|
||||
bool hint_mode;
|
||||
bool removal_mode;
|
||||
std::string traversel_rootdir;
|
||||
std::unordered_map<std::string, std::map<uint32_t, hasher::B2H>> fileblockindex;
|
||||
std::unordered_map<std::string, std::map<uint32_t, hasher::B2H>> file_block_index;
|
||||
|
||||
// List of new root hash map sub directories created during the session.
|
||||
std::unordered_set<std::string> created_htreesubdirs;
|
||||
std::unordered_set<std::string> created_htree_subdirs;
|
||||
|
||||
int traverse_and_generate(hasher::B2H &roothash);
|
||||
int update_hashtree(hasher::B2H &roothash);
|
||||
int update_hashtree_fordir(hasher::B2H &parentdirhash, const std::string &relpath, const hintpath_map::iterator hintdir_itr, const bool isrootlevel);
|
||||
int traverse_and_generate(hasher::B2H &root_hash);
|
||||
int update_hashtree(hasher::B2H &root_hash);
|
||||
int update_hashtree_fordir(hasher::B2H &parent_dir_hash, const std::string &relpath, const hintpath_map::iterator hint_dir_itr, const bool is_root_level);
|
||||
|
||||
hasher::B2H get_existingdirhash(const std::string &dirhashfile);
|
||||
int save_dirhash(const std::string &dirhashfile, hasher::B2H dirhash);
|
||||
bool should_process_dir(hintpath_map::iterator &hintsubdir_itr, const std::string &dirpath);
|
||||
bool should_process_file(const hintpath_map::iterator hintdir_itr, const std::string filepath);
|
||||
int process_file(hasher::B2H &parentdirhash, const std::string &filepath, const std::string &htreedirpath);
|
||||
int update_hashtree_entry(hasher::B2H &parentdirhash, const bool oldbhmap_exists, const hasher::B2H oldfilehash, const hasher::B2H newfilehash, const std::string &bhmapfile, const std::string &relpath);
|
||||
void populate_hintpaths_from_idxfile(const char *const idxfile);
|
||||
void insert_hintpath(const std::string &relpath);
|
||||
bool get_hinteddir_match(hintpath_map::iterator &matchitr, const std::string &dirpath);
|
||||
hasher::B2H get_existing_dir_hash(const std::string &dir_hash_file);
|
||||
int save_dir_hash(const std::string &dir_hash_file, hasher::B2H dir_hash);
|
||||
bool should_process_dir(hintpath_map::iterator &hint_subdir_itr, const std::string &dirpath);
|
||||
bool should_process_file(const hintpath_map::iterator hint_dir_itr, const std::string filepath);
|
||||
int process_file(hasher::B2H &parent_dir_hash, const std::string &filepath, const std::string &htree_dirpath);
|
||||
int update_hashtree_entry(hasher::B2H &parent_dir_hash, const bool old_bhmap_exists, const hasher::B2H old_file_hash, const hasher::B2H new_file_hash, const std::string &bhmap_file, const std::string &relpath);
|
||||
void populate_hint_paths_from_idx_file(const char *const idxfile);
|
||||
void insert_hint_path(const std::string &relpath);
|
||||
bool get_hinteddir_match(hintpath_map::iterator &match_itr, const std::string &dirpath);
|
||||
|
||||
public:
|
||||
hashtree_builder(const statedir_context &ctx);
|
||||
int generate(hasher::B2H &roothash);
|
||||
int generate(hasher::B2H &roothash, const bool force_all);
|
||||
int generate(hasher::B2H &roothash, const std::unordered_map<std::string, std::map<uint32_t, hasher::B2H>> &touchedfiles);
|
||||
hashtree_builder(const state_dir_context &ctx);
|
||||
int generate(hasher::B2H &root_hash);
|
||||
int generate(hasher::B2H &root_hash, const bool force_all);
|
||||
int generate(hasher::B2H &root_hash, const std::unordered_map<std::string, std::map<uint32_t, hasher::B2H>> &touched_files);
|
||||
};
|
||||
|
||||
} // namespace statefs
|
||||
|
||||
@@ -5,41 +5,41 @@
|
||||
namespace statefs
|
||||
{
|
||||
|
||||
std::string statehistdir;
|
||||
statedir_context current_ctx;
|
||||
std::string state_hist_dir;
|
||||
state_dir_context current_ctx;
|
||||
|
||||
void init(const std::string &statehist_dir_root)
|
||||
void init(const std::string &state_hist_dir_root)
|
||||
{
|
||||
statehistdir = realpath(statehist_dir_root.c_str(), NULL);
|
||||
state_hist_dir = realpath(state_hist_dir_root.c_str(), NULL);
|
||||
|
||||
// Initialize 0 state (current state) directory.
|
||||
current_ctx = get_statedir_context(0, true);
|
||||
current_ctx = get_state_dir_context(0, true);
|
||||
}
|
||||
|
||||
std::string get_statedir_root(const int16_t checkpointid)
|
||||
std::string get_state_dir_root(const int16_t checkpoint_id)
|
||||
{
|
||||
return statehistdir + "/" + std::to_string(checkpointid);
|
||||
return state_hist_dir + "/" + std::to_string(checkpoint_id);
|
||||
}
|
||||
|
||||
statedir_context get_statedir_context(const int16_t checkpointid, const bool createdirs)
|
||||
state_dir_context get_state_dir_context(const int16_t checkpoint_id, const bool create_dirs)
|
||||
{
|
||||
statedir_context ctx;
|
||||
ctx.rootdir = get_statedir_root(checkpointid);
|
||||
ctx.datadir = ctx.rootdir + DATA_DIR;
|
||||
ctx.blockhashmapdir = ctx.rootdir + BHMAP_DIR;
|
||||
ctx.hashtreedir = ctx.rootdir + HTREE_DIR;
|
||||
ctx.deltadir = ctx.rootdir + DELTA_DIR;
|
||||
state_dir_context ctx;
|
||||
ctx.root_dir = get_state_dir_root(checkpoint_id);
|
||||
ctx.data_dir = ctx.root_dir + DATA_DIR;
|
||||
ctx.block_hashmap_dir = ctx.root_dir + BHMAP_DIR;
|
||||
ctx.hashtree_dir = ctx.root_dir + HTREE_DIR;
|
||||
ctx.delta_dir = ctx.root_dir + DELTA_DIR;
|
||||
|
||||
if (createdirs)
|
||||
if (create_dirs)
|
||||
{
|
||||
if (!boost::filesystem::exists(ctx.datadir))
|
||||
boost::filesystem::create_directories(ctx.datadir);
|
||||
if (!boost::filesystem::exists(ctx.blockhashmapdir))
|
||||
boost::filesystem::create_directories(ctx.blockhashmapdir);
|
||||
if (!boost::filesystem::exists(ctx.hashtreedir))
|
||||
boost::filesystem::create_directories(ctx.hashtreedir);
|
||||
if (!boost::filesystem::exists(ctx.deltadir))
|
||||
boost::filesystem::create_directories(ctx.deltadir);
|
||||
if (!boost::filesystem::exists(ctx.data_dir))
|
||||
boost::filesystem::create_directories(ctx.data_dir);
|
||||
if (!boost::filesystem::exists(ctx.block_hashmap_dir))
|
||||
boost::filesystem::create_directories(ctx.block_hashmap_dir);
|
||||
if (!boost::filesystem::exists(ctx.hashtree_dir))
|
||||
boost::filesystem::create_directories(ctx.hashtree_dir);
|
||||
if (!boost::filesystem::exists(ctx.delta_dir))
|
||||
boost::filesystem::create_directories(ctx.delta_dir);
|
||||
}
|
||||
|
||||
return ctx;
|
||||
@@ -53,7 +53,7 @@ std::string get_relpath(const std::string &fullpath, const std::string &base_pat
|
||||
return relpath;
|
||||
}
|
||||
|
||||
std::string switch_basepath(const std::string &fullpath, const std::string &from_base_path, const std::string &to_base_path)
|
||||
std::string switch_base_path(const std::string &fullpath, const std::string &from_base_path, const std::string &to_base_path)
|
||||
{
|
||||
return to_base_path + get_relpath(fullpath, from_base_path);
|
||||
}
|
||||
|
||||
@@ -15,23 +15,23 @@ constexpr int16_t MAX_CHECKPOINTS = 5;
|
||||
constexpr size_t BLOCK_SIZE = 4 * 1024 * 1024; // 4MB
|
||||
|
||||
// Cache block index entry bytes length.
|
||||
constexpr size_t BLOCKINDEX_ENTRY_SIZE = 44;
|
||||
constexpr size_t BLOCK_INDEX_ENTRY_SIZE = 44;
|
||||
|
||||
// Permissions used when creating block cache and index files.
|
||||
constexpr int FILE_PERMS = 0644;
|
||||
|
||||
const char *const HASHMAP_EXT = ".bhmap";
|
||||
constexpr size_t HASHMAP_EXT_LEN = 6;
|
||||
const char *const BLOCK_HASHMAP_EXT = ".bhmap";
|
||||
constexpr size_t BLOCK_HASHMAP_EXT_LEN = 6;
|
||||
|
||||
const char *const BLOCKINDEX_EXT = ".bindex";
|
||||
constexpr size_t BLOCKINDEX_EXT_LEN = 7;
|
||||
const char *const BLOCK_INDEX_EXT = ".bindex";
|
||||
constexpr size_t BLOCK_INDEX_EXT_LEN = 7;
|
||||
|
||||
const char *const BLOCKCACHE_EXT = ".bcache";
|
||||
constexpr size_t BLOCKCACHE_EXT_LEN = 7;
|
||||
const char *const BLOCK_CACHE_EXT = ".bcache";
|
||||
constexpr size_t BLOCK_CACHE_EXT_LEN = 7;
|
||||
|
||||
const char *const IDX_NEWFILES = "/idxnew.idx";
|
||||
const char *const IDX_TOUCHEDFILES = "/idxtouched.idx";
|
||||
const char *const DIRHASH_FNAME = "dir.hash";
|
||||
const char *const IDX_NEW_FILES = "/idxnew.idx";
|
||||
const char *const IDX_TOUCHED_FILES = "/idxtouched.idx";
|
||||
const char *const DIR_HASH_FNAME = "dir.hash";
|
||||
|
||||
const char *const DATA_DIR = "/data";
|
||||
const char *const BHMAP_DIR = "/bhmap";
|
||||
@@ -41,26 +41,26 @@ const char *const DELTA_DIR = "/delta";
|
||||
/**
|
||||
* Context struct to hold all state-related directory paths.
|
||||
*/
|
||||
struct statedir_context
|
||||
struct state_dir_context
|
||||
{
|
||||
std::string rootdir; // Directory holding state sub dirs.
|
||||
std::string datadir; // Directory containing smart contract data.
|
||||
std::string blockhashmapdir; // Directory containing block hash map files.
|
||||
std::string hashtreedir; // Directory containing hash tree files (dir.hash and hard links).
|
||||
std::string deltadir; // Directory containing original smart contract data.
|
||||
std::string root_dir; // Directory holding state sub dirs.
|
||||
std::string data_dir; // Directory containing smart contract data.
|
||||
std::string block_hashmap_dir; // Directory containing block hash map files.
|
||||
std::string hashtree_dir; // Directory containing hash tree files (dir.hash and hard links).
|
||||
std::string delta_dir; // Directory containing original smart contract data.
|
||||
};
|
||||
|
||||
// Container directory to contain all checkpoints.
|
||||
extern std::string statehistdir;
|
||||
extern std::string state_hist_dir;
|
||||
|
||||
// Currently loaded state checkpoint directory context (usually checkpoint 0)
|
||||
extern statedir_context current_ctx;
|
||||
extern state_dir_context current_ctx;
|
||||
|
||||
void init(const std::string &statehist_dir_root);
|
||||
std::string get_statedir_root(const int16_t checkpointid);
|
||||
statedir_context get_statedir_context(int16_t checkpointid = 0, bool createdirs = false);
|
||||
void init(const std::string &state_hist_dir_root);
|
||||
std::string get_state_dir_root(const int16_t checkpoint_id);
|
||||
state_dir_context get_state_dir_context(int16_t checkpoint_id = 0, bool create_dirs = false);
|
||||
std::string get_relpath(const std::string &fullpath, const std::string &base_path);
|
||||
std::string switch_basepath(const std::string &fullpath, const std::string &from_base_path, const std::string &to_base_path);
|
||||
std::string switch_base_path(const std::string &fullpath, const std::string &from_base_path, const std::string &to_base_path);
|
||||
|
||||
} // namespace statefs
|
||||
|
||||
|
||||
@@ -1277,8 +1277,8 @@ int start(const char *arg0, const char *state_hist_dir, const char *fuse_mnt_dir
|
||||
const bool is_first_run = boost::filesystem::is_empty(state_hist_dir);
|
||||
|
||||
statefs::init(state_hist_dir);
|
||||
statemonitor.ctx = statefs::get_statedir_context();
|
||||
fs.source = statemonitor.ctx.datadir;
|
||||
statemonitor.ctx = statefs::get_state_dir_context();
|
||||
fs.source = statemonitor.ctx.data_dir;
|
||||
|
||||
// Create a checkpoint from the second run onwards.
|
||||
if (!is_first_run)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
namespace fusefs
|
||||
{
|
||||
int start(const char *arg0, const char *source, const char *mountpoint, const char *deltadir);
|
||||
int start(const char *arg0, const char *source, const char *mountpoint, const char *delta_dir);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -37,7 +37,7 @@ void state_monitor::create_checkpoint()
|
||||
int16_t oldest_chkpnt = MAX_CHECKPOINTS * -1;
|
||||
for (int16_t chkpnt = oldest_chkpnt; chkpnt <= -1; chkpnt++)
|
||||
{
|
||||
std::string dir = get_statedir_root(chkpnt);
|
||||
std::string dir = get_state_dir_root(chkpnt);
|
||||
|
||||
if (boost::filesystem::exists(dir))
|
||||
{
|
||||
@@ -47,21 +47,21 @@ void state_monitor::create_checkpoint()
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string dir_shift = get_statedir_root(chkpnt - 1);
|
||||
std::string dir_shift = get_state_dir_root(chkpnt - 1);
|
||||
boost::filesystem::rename(dir, dir_shift);
|
||||
}
|
||||
}
|
||||
|
||||
if (chkpnt == -1)
|
||||
{
|
||||
statedir_context ctx = get_statedir_context(0, true);
|
||||
state_dir_context ctx = get_state_dir_context(0, true);
|
||||
|
||||
// Shift 0-state delta dir to -1.
|
||||
std::string delta_1 = dir + DELTA_DIR;
|
||||
boost::filesystem::create_directories(delta_1);
|
||||
|
||||
boost::filesystem::rename(ctx.deltadir, delta_1);
|
||||
boost::filesystem::create_directories(ctx.deltadir);
|
||||
boost::filesystem::rename(ctx.delta_dir, delta_1);
|
||||
boost::filesystem::create_directories(ctx.delta_dir);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,10 +331,10 @@ int state_monitor::cache_blocks(state_file_info &fi, const off_t offset, const s
|
||||
if (fi.is_new)
|
||||
return 0;
|
||||
|
||||
uint32_t original_blockcount = ceil((double)fi.original_length / (double)BLOCK_SIZE);
|
||||
uint32_t original_block_count = ceil((double)fi.original_length / (double)BLOCK_SIZE);
|
||||
|
||||
// Check whether we have already cached the entire file.
|
||||
if (original_blockcount == fi.cached_blockids.size())
|
||||
if (original_block_count == fi.cached_blockids.size())
|
||||
return 0;
|
||||
|
||||
// Initialize fds and indexes required for caching the file.
|
||||
@@ -342,7 +342,7 @@ int state_monitor::cache_blocks(state_file_info &fi, const off_t offset, const s
|
||||
return -1;
|
||||
|
||||
// Return if incoming write is outside any of the original blocks.
|
||||
if (offset > original_blockcount * BLOCK_SIZE)
|
||||
if (offset > original_block_count * BLOCK_SIZE)
|
||||
return 0;
|
||||
|
||||
uint32_t startblock = offset / BLOCK_SIZE;
|
||||
@@ -363,20 +363,20 @@ int state_monitor::cache_blocks(state_file_info &fi, const off_t offset, const s
|
||||
|
||||
// Read the block being replaced and send to cache file.
|
||||
// Allocating block buffer on the heap to avoid filling limited stack space.
|
||||
std::unique_ptr<char[]> blockbuf = std::make_unique<char[]>(BLOCK_SIZE);
|
||||
off_t blockoffset = BLOCK_SIZE * i;
|
||||
size_t bytesread = pread(fi.readfd, blockbuf.get(), BLOCK_SIZE, BLOCK_SIZE * i);
|
||||
if (bytesread < 0)
|
||||
std::unique_ptr<char[]> block_buf = std::make_unique<char[]>(BLOCK_SIZE);
|
||||
off_t block_offset = BLOCK_SIZE * i;
|
||||
size_t bytes_read = pread(fi.readfd, block_buf.get(), BLOCK_SIZE, BLOCK_SIZE * i);
|
||||
if (bytes_read < 0)
|
||||
{
|
||||
std::cerr << errno << ": Read failed " << fi.filepath << "\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
// No more bytes to read in this file.
|
||||
if (bytesread == 0)
|
||||
if (bytes_read == 0)
|
||||
return 0;
|
||||
|
||||
if (write(fi.cachefd, blockbuf.get(), bytesread) < 0)
|
||||
if (write(fi.cachefd, block_buf.get(), bytes_read) < 0)
|
||||
{
|
||||
std::cerr << errno << ": Write to block cache failed. " << fi.filepath << "\n";
|
||||
return -1;
|
||||
@@ -388,8 +388,8 @@ int state_monitor::cache_blocks(state_file_info &fi, const off_t offset, const s
|
||||
// Entry format: [blocknum(4 bytes) | cacheoffset(8 bytes) | blockhash(32 bytes)]
|
||||
|
||||
// Calculate the block hash by combining block offset with block data.
|
||||
char entrybuf[BLOCKINDEX_ENTRY_SIZE];
|
||||
hasher::B2H hash = hasher::hash(&blockoffset, 8, blockbuf.get(), bytesread);
|
||||
char entrybuf[BLOCK_INDEX_ENTRY_SIZE];
|
||||
hasher::B2H hash = hasher::hash(&block_offset, 8, block_buf.get(), bytes_read);
|
||||
|
||||
// Original file block id.
|
||||
memcpy(entrybuf, &i, 4);
|
||||
@@ -398,7 +398,7 @@ int state_monitor::cache_blocks(state_file_info &fi, const off_t offset, const s
|
||||
memcpy(entrybuf + 4, &cacheoffset, 8);
|
||||
// The block hash.
|
||||
memcpy(entrybuf + 12, hash.data, 32);
|
||||
if (write(fi.indexfd, entrybuf, BLOCKINDEX_ENTRY_SIZE) < 0)
|
||||
if (write(fi.indexfd, entrybuf, BLOCK_INDEX_ENTRY_SIZE) < 0)
|
||||
{
|
||||
std::cerr << errno << ": Write to block index failed. " << fi.filepath << "\n";
|
||||
return -1;
|
||||
@@ -432,11 +432,11 @@ int state_monitor::prepare_caching(state_file_info &fi)
|
||||
|
||||
// Get the path of the file relative to the state dir. We maintain this same reative path for the
|
||||
// corresponding cache and index files in the cache dir.
|
||||
std::string relpath = get_relpath(fi.filepath, ctx.datadir);
|
||||
std::string relpath = get_relpath(fi.filepath, ctx.data_dir);
|
||||
|
||||
std::string tmppath;
|
||||
tmppath.reserve(ctx.deltadir.length() + relpath.length() + BLOCKCACHE_EXT_LEN);
|
||||
tmppath.append(ctx.deltadir).append(relpath).append(BLOCKCACHE_EXT);
|
||||
tmppath.reserve(ctx.delta_dir.length() + relpath.length() + BLOCK_CACHE_EXT_LEN);
|
||||
tmppath.append(ctx.delta_dir).append(relpath).append(BLOCK_CACHE_EXT);
|
||||
|
||||
// Create directory tree if not exist so we are able to create the cache and index files.
|
||||
boost::filesystem::path cachesubdir = boost::filesystem::path(tmppath).parent_path();
|
||||
@@ -455,7 +455,7 @@ int state_monitor::prepare_caching(state_file_info &fi)
|
||||
}
|
||||
|
||||
// Create and open the block index file.
|
||||
tmppath.replace(tmppath.length() - BLOCKCACHE_EXT_LEN, BLOCKINDEX_EXT_LEN, BLOCKINDEX_EXT);
|
||||
tmppath.replace(tmppath.length() - BLOCK_CACHE_EXT_LEN, BLOCK_INDEX_EXT_LEN, BLOCK_INDEX_EXT);
|
||||
fi.indexfd = open(tmppath.c_str(), O_WRONLY | O_APPEND | O_CREAT, FILE_PERMS);
|
||||
if (fi.indexfd <= 0)
|
||||
{
|
||||
@@ -501,7 +501,7 @@ int state_monitor::write_touched_file_entry(std::string_view filepath)
|
||||
{
|
||||
if (touched_fileindex_fd <= 0)
|
||||
{
|
||||
std::string index_file = ctx.deltadir + IDX_TOUCHEDFILES;
|
||||
std::string index_file = ctx.delta_dir + IDX_TOUCHED_FILES;
|
||||
touched_fileindex_fd = open(index_file.c_str(), O_WRONLY | O_APPEND | O_CREAT, FILE_PERMS);
|
||||
if (touched_fileindex_fd <= 0)
|
||||
{
|
||||
@@ -511,7 +511,7 @@ int state_monitor::write_touched_file_entry(std::string_view filepath)
|
||||
}
|
||||
|
||||
// Write the relative file path line to the index.
|
||||
filepath = filepath.substr(ctx.datadir.length(), filepath.length() - ctx.datadir.length());
|
||||
filepath = filepath.substr(ctx.data_dir.length(), filepath.length() - ctx.data_dir.length());
|
||||
write(touched_fileindex_fd, filepath.data(), filepath.length());
|
||||
write(touched_fileindex_fd, "\n", 1);
|
||||
return 0;
|
||||
@@ -523,7 +523,7 @@ int state_monitor::write_touched_file_entry(std::string_view filepath)
|
||||
*/
|
||||
int state_monitor::write_new_file_entry(std::string_view filepath)
|
||||
{
|
||||
std::string index_file = ctx.deltadir + IDX_NEWFILES;
|
||||
std::string index_file = ctx.delta_dir + IDX_NEW_FILES;
|
||||
int fd = open(index_file.c_str(), O_WRONLY | O_APPEND | O_CREAT, FILE_PERMS);
|
||||
if (fd <= 0)
|
||||
{
|
||||
@@ -532,7 +532,7 @@ int state_monitor::write_new_file_entry(std::string_view filepath)
|
||||
}
|
||||
|
||||
// Write the relative file path line to the index.
|
||||
filepath = filepath.substr(ctx.datadir.length(), filepath.length() - ctx.datadir.length());
|
||||
filepath = filepath.substr(ctx.data_dir.length(), filepath.length() - ctx.data_dir.length());
|
||||
write(fd, filepath.data(), filepath.length());
|
||||
write(fd, "\n", 1);
|
||||
close(fd);
|
||||
@@ -545,19 +545,19 @@ int state_monitor::write_new_file_entry(std::string_view filepath)
|
||||
*/
|
||||
void state_monitor::remove_new_file_entry(std::string_view filepath)
|
||||
{
|
||||
filepath = filepath.substr(ctx.datadir.length(), filepath.length() - ctx.datadir.length());
|
||||
filepath = filepath.substr(ctx.data_dir.length(), filepath.length() - ctx.data_dir.length());
|
||||
|
||||
// We create a copy of the new file index and transfer lines from first file
|
||||
// to the second file except the line matching the given filepath.
|
||||
|
||||
std::string index_file = ctx.deltadir + IDX_NEWFILES;
|
||||
std::string index_file_tmp = ctx.deltadir + IDX_NEWFILES + ".tmp";
|
||||
std::string index_file = ctx.delta_dir + IDX_NEW_FILES;
|
||||
std::string index_file_tmp = ctx.delta_dir + IDX_NEW_FILES + ".tmp";
|
||||
|
||||
std::ifstream infile(index_file);
|
||||
std::ifstream in_file(index_file);
|
||||
std::ofstream outfile(index_file_tmp);
|
||||
|
||||
bool lines_transferred = false;
|
||||
for (std::string line; std::getline(infile, line);)
|
||||
for (std::string line; std::getline(in_file, line);)
|
||||
{
|
||||
if (line != filepath) // Skip the file being removed.
|
||||
{
|
||||
@@ -566,7 +566,7 @@ void state_monitor::remove_new_file_entry(std::string_view filepath)
|
||||
}
|
||||
}
|
||||
|
||||
infile.close();
|
||||
in_file.close();
|
||||
outfile.close();
|
||||
|
||||
// Remove the old index.
|
||||
|
||||
@@ -62,7 +62,7 @@ private:
|
||||
void remove_new_file_entry(std::string_view filepath);
|
||||
|
||||
public:
|
||||
statedir_context ctx;
|
||||
state_dir_context ctx;
|
||||
void create_checkpoint();
|
||||
void oncreate(const int fd);
|
||||
void onopen(const int inodefd, const int flags);
|
||||
|
||||
@@ -9,40 +9,40 @@ namespace statefs
|
||||
{
|
||||
|
||||
// Look at new files added and delete them if still exist.
|
||||
void state_restore::delete_newfiles()
|
||||
void state_restore::delete_new_files()
|
||||
{
|
||||
std::string indexfile(ctx.deltadir);
|
||||
indexfile.append(IDX_NEWFILES);
|
||||
std::string index_file(ctx.delta_dir);
|
||||
index_file.append(IDX_NEW_FILES);
|
||||
|
||||
std::ifstream infile(indexfile);
|
||||
for (std::string file; std::getline(infile, file);)
|
||||
std::ifstream in_file(index_file);
|
||||
for (std::string file; std::getline(in_file, file);)
|
||||
{
|
||||
std::string filepath(ctx.datadir);
|
||||
std::string filepath(ctx.data_dir);
|
||||
filepath.append(file);
|
||||
|
||||
remove(filepath.c_str());
|
||||
}
|
||||
|
||||
infile.close();
|
||||
in_file.close();
|
||||
}
|
||||
|
||||
// Look at touched files and restore them.
|
||||
int state_restore::restore_touchedfiles()
|
||||
int state_restore::restore_touched_files()
|
||||
{
|
||||
std::unordered_set<std::string> processed;
|
||||
|
||||
std::string indexfile(ctx.deltadir);
|
||||
indexfile.append(IDX_TOUCHEDFILES);
|
||||
std::string index_file(ctx.delta_dir);
|
||||
index_file.append(IDX_TOUCHED_FILES);
|
||||
|
||||
std::ifstream infile(indexfile);
|
||||
for (std::string file; std::getline(infile, file);)
|
||||
std::ifstream in_file(index_file);
|
||||
for (std::string file; std::getline(in_file, file);)
|
||||
{
|
||||
// Skip if already processed.
|
||||
if (processed.count(file) > 0)
|
||||
continue;
|
||||
|
||||
std::vector<char> bindex;
|
||||
if (read_blockindex(bindex, file) != 0)
|
||||
if (read_block_index(bindex, file) != 0)
|
||||
return -1;
|
||||
|
||||
if (restore_blocks(file, bindex) != 0)
|
||||
@@ -52,23 +52,23 @@ int state_restore::restore_touchedfiles()
|
||||
processed.emplace(file);
|
||||
}
|
||||
|
||||
infile.close();
|
||||
in_file.close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Read the delta block index.
|
||||
int state_restore::read_blockindex(std::vector<char> &buffer, std::string_view file)
|
||||
int state_restore::read_block_index(std::vector<char> &buffer, std::string_view file)
|
||||
{
|
||||
std::string bindexfile(ctx.deltadir);
|
||||
bindexfile.append(file).append(BLOCKINDEX_EXT);
|
||||
std::ifstream infile(bindexfile, std::ios::binary | std::ios::ate);
|
||||
std::streamsize idxsize = infile.tellg();
|
||||
infile.seekg(0, std::ios::beg);
|
||||
std::string bindex_file(ctx.delta_dir);
|
||||
bindex_file.append(file).append(BLOCK_INDEX_EXT);
|
||||
std::ifstream in_file(bindex_file, std::ios::binary | std::ios::ate);
|
||||
std::streamsize idx_size = in_file.tellg();
|
||||
in_file.seekg(0, std::ios::beg);
|
||||
|
||||
buffer.resize(idxsize);
|
||||
if (!infile.read(buffer.data(), idxsize))
|
||||
buffer.resize(idx_size);
|
||||
if (!in_file.read(buffer.data(), idx_size))
|
||||
{
|
||||
LOG_ERR << errno << ": Read failed " << bindexfile;
|
||||
LOG_ERR << errno << ": Read failed " << bindex_file;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -78,71 +78,71 @@ int state_restore::read_blockindex(std::vector<char> &buffer, std::string_view f
|
||||
// Restore blocks mentioned in the delta block index.
|
||||
int state_restore::restore_blocks(std::string_view file, const std::vector<char> &bindex)
|
||||
{
|
||||
int bcachefd = 0, orifilefd = 0;
|
||||
const char *idxptr = bindex.data();
|
||||
int bcache_fd = 0, ori_file_fd = 0;
|
||||
const char *idx_ptr = bindex.data();
|
||||
|
||||
// First 8 bytes of the index contains the supposed length of the original file.
|
||||
off_t originallen = 0;
|
||||
memcpy(&originallen, idxptr, 8);
|
||||
off_t original_len = 0;
|
||||
memcpy(&original_len, idx_ptr, 8);
|
||||
|
||||
// Open block cache file.
|
||||
{
|
||||
std::string bcachefile(ctx.deltadir);
|
||||
bcachefile.append(file).append(BLOCKCACHE_EXT);
|
||||
bcachefd = open(bcachefile.c_str(), O_RDONLY);
|
||||
if (bcachefd <= 0)
|
||||
std::string bcache_file(ctx.delta_dir);
|
||||
bcache_file.append(file).append(BLOCK_CACHE_EXT);
|
||||
bcache_fd = open(bcache_file.c_str(), O_RDONLY);
|
||||
if (bcache_fd <= 0)
|
||||
{
|
||||
LOG_ERR << errno << ": Open failed " << bcachefile;
|
||||
LOG_ERR << errno << ": Open failed " << bcache_file;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Create or Open original file.
|
||||
{
|
||||
std::string originalfile(ctx.datadir);
|
||||
originalfile.append(file);
|
||||
std::string original_file(ctx.data_dir);
|
||||
original_file.append(file);
|
||||
|
||||
// Create directory tree if not exist so we are able to create the file.
|
||||
boost::filesystem::path filedir = boost::filesystem::path(originalfile).parent_path();
|
||||
boost::filesystem::path filedir = boost::filesystem::path(original_file).parent_path();
|
||||
if (created_dirs.count(filedir.string()) == 0)
|
||||
{
|
||||
boost::filesystem::create_directories(filedir);
|
||||
created_dirs.emplace(filedir.string());
|
||||
}
|
||||
|
||||
orifilefd = open(originalfile.c_str(), O_WRONLY | O_CREAT, FILE_PERMS);
|
||||
if (orifilefd <= 0)
|
||||
ori_file_fd = open(original_file.c_str(), O_WRONLY | O_CREAT, FILE_PERMS);
|
||||
if (ori_file_fd <= 0)
|
||||
{
|
||||
LOG_ERR << errno << ": Open failed " << originalfile;
|
||||
LOG_ERR << errno << ": Open failed " << original_file;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the blocks as specified in block index.
|
||||
for (uint32_t idxoffset = 8; idxoffset < bindex.size();)
|
||||
for (uint32_t idx_offset = 8; idx_offset < bindex.size();)
|
||||
{
|
||||
// Find the block no. of where this block is from in the original file.
|
||||
uint32_t blockno = 0;
|
||||
memcpy(&blockno, idxptr + idxoffset, 4);
|
||||
idxoffset += 4;
|
||||
off_t orifileoffset = blockno * BLOCK_SIZE;
|
||||
uint32_t block_no = 0;
|
||||
memcpy(&block_no, idx_ptr + idx_offset, 4);
|
||||
idx_offset += 4;
|
||||
off_t ori_file_offset = block_no * BLOCK_SIZE;
|
||||
|
||||
// Find the offset where the block is located in the block cache file.
|
||||
off_t bcacheoffset;
|
||||
memcpy(&bcacheoffset, idxptr + idxoffset, 8);
|
||||
idxoffset += 40; // Skip the hash(32)
|
||||
off_t bcache_offset;
|
||||
memcpy(&bcache_offset, idx_ptr + idx_offset, 8);
|
||||
idx_offset += 40; // Skip the hash(32)
|
||||
|
||||
// Transfer the cached block to the target file.
|
||||
copy_file_range(bcachefd, &bcacheoffset, orifilefd, &orifileoffset, BLOCK_SIZE, 0);
|
||||
copy_file_range(bcache_fd, &bcache_offset, ori_file_fd, &ori_file_offset, BLOCK_SIZE, 0);
|
||||
}
|
||||
|
||||
// If the target file is bigger than the original size, truncate it to the original size.
|
||||
off_t currentlen = lseek(orifilefd, 0, SEEK_END);
|
||||
if (currentlen > originallen)
|
||||
ftruncate(orifilefd, originallen);
|
||||
off_t current_len = lseek(ori_file_fd, 0, SEEK_END);
|
||||
if (current_len > original_len)
|
||||
ftruncate(ori_file_fd, original_len);
|
||||
|
||||
close(bcachefd);
|
||||
close(orifilefd);
|
||||
close(bcache_fd);
|
||||
close(ori_file_fd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -154,12 +154,12 @@ void state_restore::rewind_checkpoints()
|
||||
// we need to shift each history delta by 1 place.
|
||||
|
||||
// Delete the state 0 (current) delta.
|
||||
boost::filesystem::remove_all(ctx.deltadir);
|
||||
boost::filesystem::remove_all(ctx.delta_dir);
|
||||
|
||||
int16_t oldest_chkpnt = (MAX_CHECKPOINTS + 1) * -1; // +1 because we maintain one extra checkpoint in case of rollbacks.
|
||||
for (int16_t chkpnt = -1; chkpnt >= oldest_chkpnt; chkpnt--)
|
||||
{
|
||||
std::string dir = get_statedir_root(chkpnt);
|
||||
std::string dir = get_state_dir_root(chkpnt);
|
||||
|
||||
if (boost::filesystem::exists(dir))
|
||||
{
|
||||
@@ -167,12 +167,12 @@ void state_restore::rewind_checkpoints()
|
||||
{
|
||||
// Shift -1 state delta dir to 0-state and delete -1 dir.
|
||||
std::string delta_1 = dir + DELTA_DIR;
|
||||
boost::filesystem::rename(delta_1, ctx.deltadir);
|
||||
boost::filesystem::rename(delta_1, ctx.delta_dir);
|
||||
boost::filesystem::remove_all(dir);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string dirshift = get_statedir_root(chkpnt + 1);
|
||||
std::string dirshift = get_state_dir_root(chkpnt + 1);
|
||||
boost::filesystem::rename(dir, dirshift);
|
||||
}
|
||||
}
|
||||
@@ -180,17 +180,17 @@ void state_restore::rewind_checkpoints()
|
||||
}
|
||||
|
||||
// Rolls back current state to previous state.
|
||||
int state_restore::rollback(hasher::B2H &roothash)
|
||||
int state_restore::rollback(hasher::B2H &root_hash)
|
||||
{
|
||||
ctx = get_statedir_context();
|
||||
ctx = get_state_dir_context();
|
||||
|
||||
delete_newfiles();
|
||||
if (restore_touchedfiles() == -1)
|
||||
delete_new_files();
|
||||
if (restore_touched_files() == -1)
|
||||
return -1;
|
||||
|
||||
// Update hash tree.
|
||||
hashtree_builder htreebuilder(ctx);
|
||||
htreebuilder.generate(roothash);
|
||||
hashtree_builder htree_builder(ctx);
|
||||
htree_builder.generate(root_hash);
|
||||
|
||||
rewind_checkpoints();
|
||||
|
||||
|
||||
@@ -11,16 +11,16 @@ namespace statefs
|
||||
class state_restore
|
||||
{
|
||||
private:
|
||||
statedir_context ctx;
|
||||
state_dir_context ctx;
|
||||
std::unordered_set<std::string> created_dirs;
|
||||
void delete_newfiles();
|
||||
int restore_touchedfiles();
|
||||
int read_blockindex(std::vector<char> &buffer, std::string_view file);
|
||||
void delete_new_files();
|
||||
int restore_touched_files();
|
||||
int read_block_index(std::vector<char> &buffer, std::string_view file);
|
||||
int restore_blocks(std::string_view file, const std::vector<char> &bindex);
|
||||
void rewind_checkpoints();
|
||||
|
||||
public:
|
||||
int rollback(hasher::B2H &roothash);
|
||||
int rollback(hasher::B2H &root_hash);
|
||||
};
|
||||
|
||||
} // namespace statefs
|
||||
|
||||
@@ -17,7 +17,7 @@ std::unordered_map<std::string, std::map<uint32_t, hasher::B2H>> touched_files;
|
||||
*/
|
||||
bool is_dir_exists(const std::string &dir_relpath)
|
||||
{
|
||||
const std::string full_path = current_ctx.datadir + dir_relpath;
|
||||
const std::string full_path = current_ctx.data_dir + dir_relpath;
|
||||
return boost::filesystem::exists(full_path);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ int get_fs_entry_hashes(std::unordered_map<std::string, p2p::state_fs_hash_entry
|
||||
if (expected_hash != hasher::B2H_empty)
|
||||
{
|
||||
// Check whether the existing block hash matches expected hash.
|
||||
const std::string dir_hash_path = current_ctx.hashtreedir + dir_relpath + DIRHASH_FNAME;
|
||||
const std::string dir_hash_path = current_ctx.hashtree_dir + dir_relpath + DIR_HASH_FNAME;
|
||||
|
||||
hasher::B2H existsing_hash;
|
||||
if (read_file_bytes(&existsing_hash, dir_hash_path.c_str(), 0, hasher::HASH_SIZE) == -1)
|
||||
@@ -44,7 +44,7 @@ int get_fs_entry_hashes(std::unordered_map<std::string, p2p::state_fs_hash_entry
|
||||
return -1;
|
||||
}
|
||||
|
||||
const std::string full_path = current_ctx.datadir + dir_relpath;
|
||||
const std::string full_path = current_ctx.data_dir + dir_relpath;
|
||||
for (const boost::filesystem::directory_entry &dentry : boost::filesystem::directory_iterator(full_path))
|
||||
{
|
||||
const boost::filesystem::path p = dentry.path();
|
||||
@@ -60,12 +60,12 @@ int get_fs_entry_hashes(std::unordered_map<std::string, p2p::state_fs_hash_entry
|
||||
|
||||
if (fs_entry.is_file)
|
||||
{
|
||||
hash_path = current_ctx.blockhashmapdir + fsentry_relpath + HASHMAP_EXT;
|
||||
hash_path = current_ctx.block_hashmap_dir + fsentry_relpath + BLOCK_HASHMAP_EXT;
|
||||
}
|
||||
else
|
||||
{
|
||||
fsentry_relpath += "/";
|
||||
hash_path = current_ctx.hashtreedir + fsentry_relpath + DIRHASH_FNAME;
|
||||
hash_path = current_ctx.hashtree_dir + fsentry_relpath + DIR_HASH_FNAME;
|
||||
// Skip the directory if it doesn't contain the dir.hash file.
|
||||
// By that we assume the directory is empty so we're not interested in it.
|
||||
if (!boost::filesystem::exists(hash_path))
|
||||
@@ -86,7 +86,7 @@ int get_fs_entry_hashes(std::unordered_map<std::string, p2p::state_fs_hash_entry
|
||||
*/
|
||||
int get_block_hash_map(std::vector<uint8_t> &vec, const std::string &file_relpath, const hasher::B2H expected_hash)
|
||||
{
|
||||
const std::string bhmap_path = current_ctx.blockhashmapdir + file_relpath + HASHMAP_EXT;
|
||||
const std::string bhmap_path = current_ctx.block_hashmap_dir + file_relpath + BLOCK_HASHMAP_EXT;
|
||||
|
||||
if (expected_hash != hasher::B2H_empty)
|
||||
{
|
||||
@@ -119,11 +119,11 @@ int get_block_hash_map(std::vector<uint8_t> &vec, const std::string &file_relpat
|
||||
*/
|
||||
int get_file_length(const std::string &file_relpath)
|
||||
{
|
||||
std::string full_path = current_ctx.datadir + file_relpath;
|
||||
std::string full_path = current_ctx.data_dir + file_relpath;
|
||||
int fd = open(full_path.c_str(), O_RDONLY);
|
||||
if (fd == -1)
|
||||
{
|
||||
LOG_ERR << errno << "Open failed " << full_path;
|
||||
LOG_ERR << errno << " Open failed " << full_path;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ int get_block(std::vector<uint8_t> &vec, const std::string &file_relpath, const
|
||||
// Check whether the existing block hash matches expected hash.
|
||||
if (expected_hash != hasher::B2H_empty)
|
||||
{
|
||||
std::string bhmap_path = current_ctx.blockhashmapdir + file_relpath + HASHMAP_EXT;
|
||||
std::string bhmap_path = current_ctx.block_hashmap_dir + file_relpath + BLOCK_HASHMAP_EXT;
|
||||
hasher::B2H existing_hash = hasher::B2H_empty;
|
||||
|
||||
if (read_file_bytes(&existing_hash, bhmap_path.c_str(), (block_id + 1) * hasher::HASH_SIZE, hasher::HASH_SIZE) == -1)
|
||||
@@ -152,7 +152,7 @@ int get_block(std::vector<uint8_t> &vec, const std::string &file_relpath, const
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string full_path = current_ctx.datadir + file_relpath;
|
||||
std::string full_path = current_ctx.data_dir + file_relpath;
|
||||
vec.resize(BLOCK_SIZE);
|
||||
int read_bytes = read_file_bytes(vec.data(), full_path.c_str(), block_id * BLOCK_SIZE, BLOCK_SIZE);
|
||||
|
||||
@@ -168,7 +168,7 @@ int get_block(std::vector<uint8_t> &vec, const std::string &file_relpath, const
|
||||
*/
|
||||
void create_dir(const std::string &dir_relpath)
|
||||
{
|
||||
const std::string full_path = current_ctx.datadir + dir_relpath;
|
||||
const std::string full_path = current_ctx.data_dir + dir_relpath;
|
||||
boost::filesystem::create_directories(full_path);
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ void create_dir(const std::string &dir_relpath)
|
||||
*/
|
||||
int delete_dir(const std::string &dir_relpath)
|
||||
{
|
||||
std::string full_dir_path = current_ctx.datadir + dir_relpath;
|
||||
std::string full_dir_path = current_ctx.data_dir + dir_relpath;
|
||||
|
||||
const boost::filesystem::directory_iterator itr_end;
|
||||
for (boost::filesystem::directory_iterator itr(full_dir_path); itr != itr_end; itr++)
|
||||
@@ -192,7 +192,7 @@ int delete_dir(const std::string &dir_relpath)
|
||||
|
||||
// Add the deleted file rel path to the touched files list.
|
||||
touched_files.emplace(
|
||||
get_relpath(p.string(), current_ctx.datadir),
|
||||
get_relpath(p.string(), current_ctx.data_dir),
|
||||
std::map<uint32_t, hasher::B2H>());
|
||||
}
|
||||
}
|
||||
@@ -209,7 +209,7 @@ int delete_dir(const std::string &dir_relpath)
|
||||
*/
|
||||
int delete_file(const std::string &file_relpath)
|
||||
{
|
||||
std::string full_path = current_ctx.datadir + file_relpath;
|
||||
std::string full_path = current_ctx.data_dir + file_relpath;
|
||||
if (!boost::filesystem::remove(full_path))
|
||||
return -1;
|
||||
|
||||
@@ -223,11 +223,11 @@ int delete_file(const std::string &file_relpath)
|
||||
*/
|
||||
int truncate_file(const std::string &file_relpath, const size_t newsize)
|
||||
{
|
||||
std::string full_path = current_ctx.datadir + file_relpath;
|
||||
std::string full_path = current_ctx.data_dir + file_relpath;
|
||||
int fd = open(full_path.c_str(), O_WRONLY | O_CREAT, FILE_PERMS);
|
||||
if (fd == -1)
|
||||
{
|
||||
LOG_ERR << errno << "Open failed " << full_path;
|
||||
LOG_ERR << errno << " Open failed " << full_path;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -252,11 +252,11 @@ int truncate_file(const std::string &file_relpath, const size_t newsize)
|
||||
*/
|
||||
int write_block(const std::string &file_relpath, const uint32_t block_id, const void *buf, const size_t len)
|
||||
{
|
||||
std::string full_path = current_ctx.datadir + file_relpath;
|
||||
std::string full_path = current_ctx.data_dir + file_relpath;
|
||||
int fd = open(full_path.c_str(), O_WRONLY | O_CREAT, FILE_PERMS);
|
||||
if (fd == -1)
|
||||
{
|
||||
LOG_ERR << errno << "Open failed " << full_path;
|
||||
LOG_ERR << errno << " Open failed " << full_path;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -265,7 +265,7 @@ int write_block(const std::string &file_relpath, const uint32_t block_id, const
|
||||
close(fd);
|
||||
if (ret == -1)
|
||||
{
|
||||
LOG_ERR << errno << "Write failed " << full_path;
|
||||
LOG_ERR << errno << " Write failed " << full_path;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -304,7 +304,7 @@ int read_file_bytes(void *buf, const char *filepath, const off_t start, const si
|
||||
int fd = open(filepath, O_RDONLY);
|
||||
if (fd == -1)
|
||||
{
|
||||
LOG_ERR << errno << "Open failed " << filepath;
|
||||
LOG_ERR << errno << " Open failed " << filepath;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -312,7 +312,7 @@ int read_file_bytes(void *buf, const char *filepath, const off_t start, const si
|
||||
close(fd);
|
||||
if (read_bytes <= 0)
|
||||
{
|
||||
LOG_ERR << errno << "Read failed " << filepath;
|
||||
LOG_ERR << errno << " Read failed " << filepath;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -331,7 +331,7 @@ int read_file_bytes_to_end(std::vector<uint8_t> &vec, const char *filepath, cons
|
||||
int fd = open(filepath, O_RDONLY);
|
||||
if (fd == -1)
|
||||
{
|
||||
LOG_ERR << errno << "Open failed " << filepath;
|
||||
LOG_ERR << errno << " Open failed " << filepath;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -346,7 +346,7 @@ int read_file_bytes_to_end(std::vector<uint8_t> &vec, const char *filepath, cons
|
||||
close(fd);
|
||||
if (read_bytes <= 0)
|
||||
{
|
||||
LOG_ERR << errno << "Read failed " << filepath;
|
||||
LOG_ERR << errno << " Read failed " << filepath;
|
||||
return -1;
|
||||
}
|
||||
vec.resize(read_bytes);
|
||||
|
||||
Reference in New Issue
Block a user