Contract state monitoring and rollback infrastructure. (#61)

This commit is contained in:
Ravin Perera
2019-11-25 19:30:15 +05:30
committed by GitHub
parent bebdace519
commit 0dde10c306
32 changed files with 3222 additions and 230 deletions

View File

@@ -1,2 +1,4 @@
**/**
!build/hpcore
!build/hpcore
!build/hpstatemon
!libfuse3.so.3

View File

@@ -40,10 +40,19 @@ target_link_libraries(hpsupport
${CMAKE_DL_LIBS}
)
add_library(hpstatefs
src/statefs/hasher.cpp
src/statefs/state_common.cpp
src/statefs/hashmap_builder.cpp
src/statefs/hashtree_builder.cpp
src/statefs/state_restore.cpp
)
target_link_libraries(hpstatefs hpsupport)
add_library(hpproc
src/proc/proc.cpp
)
target_link_libraries(hpproc hpsupport)
target_link_libraries(hpproc hpsupport hpstatefs)
add_library(hpbill
src/bill/corebill.cpp
@@ -82,7 +91,6 @@ target_link_libraries(hpusr hpsupport hpsock hpschema)
add_library(hpcons
src/cons/cons.cpp
src/cons/ledger_handler.cpp
src/cons/statemap_handler.cpp
)
target_link_libraries(hpcons hpsupport hpproc hpp2p hpusr)
@@ -100,6 +108,21 @@ target_link_libraries(hpcore
hpcons
)
add_executable(hpstatemon
src/statefs/state_monitor/fusefs.cpp
src/statefs/state_monitor/state_monitor.cpp
src/statefs/hasher.cpp
src/statefs/state_common.cpp
)
target_link_libraries(hpstatemon
hpsupport
libfuse3.so.3
)
add_dependencies(hpcore
hpstatemon
)
target_precompile_headers(hpsupport PUBLIC src/pchheader.hpp)
target_precompile_headers(hpcore REUSE_FROM hpsupport)
target_precompile_headers(hpsock REUSE_FROM hpsupport)
@@ -107,6 +130,7 @@ target_precompile_headers(hpschema REUSE_FROM hpsupport)
target_precompile_headers(hpp2p REUSE_FROM hpsupport)
target_precompile_headers(hpusr REUSE_FROM hpsupport)
target_precompile_headers(hpcons REUSE_FROM hpsupport)
target_precompile_headers(hpstatemon REUSE_FROM hpsupport)
# Create docker image from hpcore build output with 'make docker'
# Requires docker to be runnable without 'sudo'

View File

@@ -2,7 +2,14 @@
# Otherwise, hpcore itself can run on any docker image like ubuntu or debian without NodeJs.
FROM node:10.17.0-buster-slim
# Copy fuse shared library and register it.
COPY ./libfuse3.so.3 /usr/local/lib/
RUN ldconfig
# Install fuse.
RUN apt-get update && apt-get install -y fuse && rm -rf /var/lib/apt/lists/*
# hpcore binary is copied to /hp directory withtin the docker image.
WORKDIR /hp
COPY ./build/hpcore .
COPY ./build/hpstatemon .
ENTRYPOINT ["/hp/hpcore"]

View File

@@ -13,6 +13,7 @@ A C++ version of hotpocket designed for production envrionments, original protot
* RapidJSON - http://rapidjson.org
* P2P Protocol - https://google.github.io/flatbuffers/
* TLS - https://www.openssl.org/
* Fuse filesystem - https://github.com/libfuse/libfuse
## Steps to setup Hot Pocket
@@ -68,6 +69,13 @@ Example: When you make a change to `p2pmsg_content_.fbc` defnition file, you nee
3. Run `./config && make`
4. Run `sudo make install`
#### Install libfuse
1. `sudo apt-get install -y meson ninja-build pkg-config`
2. Download [libfuse 3.8](https://github.com/libfuse/libfuse/releases/download/fuse-3.8.0/fuse-3.8.0.tar.xz) and extract.
3. `mkdir build; cd build`
4. `meson .. && ninja`
6. `sudo ninja install`
#### Run ldconfig
`sudo ldconfig`
@@ -98,4 +106,6 @@ Code is divided into subsystems via namespaces.
**sock::** Handles generic web sockets functionality. Mainly acts as a wrapper for boost/beast.
**util::** Contains shared data structures/helper functions used by multiple subsystems.
**util::** Contains shared data structures/helper functions used by multiple subsystems.
**statefs::** Fuse-based state filesystem monitoring and contract state maintenence subsystem.

View File

@@ -50,8 +50,9 @@ do
binargs: './bin/contract.js', \
peerport: ${peerport}, \
pubport: ${pubport}, \
roundtime: 1000,
loglevel: 'debug'
roundtime: 1000, \
loglevel: 'debug', \
loggers:['console'] \
}, null, 2)" > hp.cfg
rm tmp.json

View File

@@ -23,5 +23,6 @@ let pubport=8080+$n
# We specify --name for each node so it will be the virtual dns name for each node.
docker run --rm -t -i --network=hpnet --name=node${n} \
-p ${pubport}:${pubport} \
--device /dev/fuse --cap-add SYS_ADMIN --security-opt apparmor:unconfined \
--mount type=bind,source=${clusterloc}/node${n},target=/contract \
hpcore:latest run /contract

View File

@@ -6,11 +6,10 @@ const fs = require('fs')
//console.log("===Sample contract started===");
//console.log("Contract args received from hp: " + input);
let hpargsstr = fs.readFileSync(0, 'utf8');
let hpargs = JSON.parse(hpargsstr);
let hpargs = JSON.parse( fs.readFileSync(0, 'utf8'));
// We just save execution args as an example state file change.
fs.appendFileSync("state/execargs.txt", hpargsstr + "\n");
fs.appendFileSync("state/exects.txt", "ts:" + hpargs.ts + "\n");
Object.keys(hpargs.usrfd).forEach(function (key, index) {
let userfds = hpargs.usrfd[key];

BIN
libfuse3.so.3 Executable file

Binary file not shown.

View File

@@ -17,15 +17,6 @@ contract_config cfg;
const static char *MODE_PASSIVE = "passive";
const static char *MODE_ACTIVE = "active";
// provide a safe std::string overload for realpath
std::string realpath(std::string path)
{
std::array<char, PATH_MAX> buffer;
::realpath(path.c_str(), buffer.data());
buffer[PATH_MAX] = '\0';
return buffer.data();
}
/**
* Loads and initializes the contract config for execution. Must be called once during application startup.
* @return 0 for success. -1 for failure.
@@ -91,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.statemapdir);
boost::filesystem::create_directories(ctx.statehistdir);
//Create config file with default settings.
@@ -128,9 +119,16 @@ int create_contract()
* Updates the contract context with directory paths based on provided base directory.
* This is called after parsing HP command line arg in order to populate the ctx.
*/
void set_contract_dir_paths(std::string basedir)
void set_contract_dir_paths(std::string exepath, std::string basedir)
{
if (basedir == "")
if (exepath.empty())
{
// this code branch will never execute the way main is currently coded, but it might change in future
std::cerr << "Executable path must be specified\n";
exit(1);
}
if (basedir.empty())
{
// this code branch will never execute the way main is currently coded, but it might change in future
std::cerr << "a contract directory must be specified\n";
@@ -138,7 +136,10 @@ void set_contract_dir_paths(std::string basedir)
}
// resolving the path through realpath will remove any trailing slash if present
basedir = realpath(basedir);
basedir = util::realpath(basedir);
ctx.exedir = boost::filesystem::path(util::realpath(exepath)).parent_path().string();
ctx.statemonexepath = ctx.exedir + "/" + "hpstatemon";
ctx.contractdir = basedir;
ctx.configdir = basedir + "/cfg";
@@ -147,7 +148,7 @@ void set_contract_dir_paths(std::string basedir)
ctx.tlscertfile = ctx.configdir + "/tlscert.pem";
ctx.histdir = basedir + "/hist";
ctx.statedir = basedir + "/state";
ctx.statemapdir = basedir + "/statemap";
ctx.statehistdir = basedir + "/statehist";
ctx.logdir = basedir + "/log";
}
@@ -503,7 +504,7 @@ int validate_contract_dir_paths()
ctx.configfile,
ctx.histdir,
ctx.statedir,
ctx.statemapdir,
ctx.statehistdir,
ctx.tlskeyfile,
ctx.tlscertfile};

View File

@@ -24,11 +24,13 @@ enum OPERATING_MODE
struct contract_ctx
{
std::string command; // The CLI command issued to launch HotPocket
std::string exedir; // Hot Pocket executable dir.
std::string statemonexepath;// State monitor executable file path.
std::string contractdir; // Contract base directory full path
std::string histdir; // Contract history dir full path
std::string statedir; // Contract state dir full path
std::string statemapdir; // Contract state map dir (.merkel files) 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 logdir; // Contract log dir full path
std::string configdir; // Contract config dir full path
std::string configfile; // Full path to the contract config file
@@ -90,7 +92,7 @@ int rekey();
int create_contract();
void set_contract_dir_paths(std::string basedir);
void set_contract_dir_paths(std::string exepath, std::string basedir);
//------Internal-use functions for this namespace.

View File

@@ -12,7 +12,6 @@
#include "../crypto.hpp"
#include "../proc/proc.hpp"
#include "ledger_handler.hpp"
#include "statemap_handler.hpp"
#include "cons.hpp"
namespace p2pmsg = fbschema::p2pmsg;
@@ -590,7 +589,6 @@ void apply_ledger(const p2p::proposal &cons_prop)
extract_user_outputs_from_contract_bufmap(useriobufmap);
broadcast_npl_output(nplbufpair.output);
update_state_blockmap(updated_blocks);
}
/**

View File

@@ -1,177 +0,0 @@
#include "../pchheader.hpp"
#include "../conf.hpp"
#include "../hplog.hpp"
#include "../proc/proc.hpp"
namespace cons
{
constexpr size_t BLOCK_SIZE = 4 * 1024 * 1024; //this is the logical block size files are decomposed in, it's also the size of the merkle tree
constexpr size_t HASH_SIZE = crypto_generichash_blake2b_BYTES;
constexpr size_t MAX_HASHES = BLOCK_SIZE / HASH_SIZE;
constexpr const char *MERKLE_EXTENSION = ".merkle";
struct B2H // blake2b hash is 32 bytes which we store as 4 quad words
{
uint64_t data[4];
};
// provide some helper functions for working with 32 byte hash type
bool operator==(B2H &lhs, B2H &rhs)
{
return lhs.data[0] == rhs.data[0] && lhs.data[1] == rhs.data[1] && lhs.data[2] == rhs.data[2] && lhs.data[3] == rhs.data[3];
}
// the actual hash function, note that the B2H datatype is always passed by value being only 4 quadwords
B2H hash(const void *ptr, size_t len)
{
B2H ret;
crypto_generichash_blake2b_state state;
crypto_generichash_blake2b_init(&state, NULL, 0, HASH_SIZE);
crypto_generichash_blake2b_update(&state,
reinterpret_cast<const unsigned char *>(ptr), len);
crypto_generichash_blake2b_final(
&state,
reinterpret_cast<unsigned char *>(&ret),
HASH_SIZE);
return ret;
}
/**
* Updates the .merkel block map for the given state file.
* @param filepath Full path of the state file.
* @param hinted_blocks Set of updated file block ids. If empty full merkel block map will be recomputed.
*/
int update_file_blockmap(const std::string &filepath, const std::set<uint32_t> &hinted_blocks)
{
// .merkel file path will be corresponding path in "statemap" directory.
std::string merkle_fn;
const size_t relative_path_len = filepath.length() - conf::ctx.statedir.length();
merkle_fn.reserve(conf::ctx.statemapdir.length() + relative_path_len + 7);
merkle_fn.append(conf::ctx.statemapdir);
merkle_fn.append(filepath.substr(conf::ctx.statedir.length(), relative_path_len));
merkle_fn.append(MERKLE_EXTENSION);
// To benefit from hint mode, the .merkle file must already exist. If not we simply disable hint mode
// because we anyway have to rebuild entire merkle file from scratch.
bool hint_mode = !hinted_blocks.empty();
if (access(merkle_fn.c_str(), F_OK) == -1)
hint_mode = false;
// open the target file for which we are building or updating a merkle tree
FILE *f = fopen(filepath.c_str(), "rb");
if (!f)
{
LOG_ERR << "Failed to open state file: " << filepath << " for reading.";
return -1;
}
// the merkle tree structure is only 4mb and could technically sit on stack in most cases but
// TODO: ******Why can't we allocate this on the stack?
auto merkle_tree = std::make_unique<B2H[]>(MAX_HASHES);
// same with the read buffer
auto read_buffer = std::make_unique<uint8_t[]>(BLOCK_SIZE);
// this iterator will be used if we are in hint mode
auto hint = hinted_blocks.begin();
size_t block_counter = 0;
size_t block_location = 0;
while (true)
{
// if hint blocks have been specified we'll seek to specific blocks in the file based on hint list.
if (hint_mode)
{
// check if we've run out of elements
if (hint == hinted_blocks.end())
break;
// get the next block on their list
block_location = *hint++;
// seek the file cursor to the block
fseek(f, block_location * BLOCK_SIZE, SEEK_SET);
}
// read the block
int bytesread = fread(read_buffer.get(), 1, BLOCK_SIZE, f);
if (bytesread <= 0)
break;
// calculate the block hash
merkle_tree[block_location++] = hash(read_buffer.get(), std::min(BLOCK_SIZE, (size_t)bytesread));
block_counter++;
}
fclose(f);
// now that we've computed all the block hashes we are interested in we have to deal with the .merkle file
// open the .merkle file
// if we are in hint_mode we will open it in rb+ which will preserve its contents
// otherwise we will truncate it if it already exists, because we will have to overwrite everything anyway
FILE *fm = fopen(merkle_fn.c_str(), (hint_mode ? "rb+" : "wb+"));
if (!fm)
{
LOG_ERR << "Failed to open merkle file: " << filepath << " for writing.";
return -1;
}
// get the size of the .merkle file
fseek(fm, 0L, SEEK_END);
const size_t len = ftell(fm);
rewind(fm);
// write the updated hashes
if (hint_mode)
{
// write selectively the updated block hashes
const int fd = fileno(fm);
for (int block : hinted_blocks)
pwrite(fd, &(merkle_tree[block]), HASH_SIZE, HASH_SIZE * block);
}
else
{
// write the whole tree to the file
fwrite(reinterpret_cast<void *>(merkle_tree.get()), 1, std::min(BLOCK_SIZE, HASH_SIZE * block_counter), fm);
}
// compute the root hash
B2H root_hash;
if (hint_mode)
{
// if we only updated selective hashes (hint mode) then now we need to compute a hash over the whole merkle file
// so we first need to read it in
rewind(f);
int bytesread = fread(read_buffer.get(), 1, BLOCK_SIZE, f);
if (bytesread <= 0)
fprintf(stderr, "could not read merkle file after writing to it?!\n");
// now simply compute the hash of what we just read, that's our root hash
root_hash = hash(read_buffer.get(), std::min(BLOCK_SIZE, (size_t)bytesread));
}
else
{
// if we've just written out the whole merkle tree we already know it
root_hash = hash(merkle_tree.get(), std::min(BLOCK_SIZE, HASH_SIZE * block_counter));
}
fclose(fm);
}
/**
* Updates the state block map for the files updated as specified by the provided blockmap.
* TODO: This doesn't currently support deleted file tracking.
*/
void update_state_blockmap(const proc::contract_fblockmap_t &updates)
{
for (const auto &[filepath, blocks] : updates)
{
update_file_blockmap(filepath, blocks);
}
}
} // namespace cons

View File

@@ -1,11 +0,0 @@
#ifndef _HP_CONS_STATEMAP_HANDLER_
#define _HP_CONS_STATEMAP_HANDLER_
#include "../proc/proc.hpp"
namespace cons
{
void update_state_blockmap(const proc::contract_fblockmap_t &updates);
}
#endif

View File

@@ -11,6 +11,7 @@
#include "usr/usr.hpp"
#include "p2p/p2p.hpp"
#include "cons/cons.hpp"
#include "statefs/state_common.hpp"
/**
* Parses CLI args and extracts hot pocket command and parameters given.
@@ -35,7 +36,7 @@ int parse_cmd(int argc, char **argv)
{
// We inform the conf subsystem to populate the contract directory context values
// based on the directory argument from the command line.
conf::set_contract_dir_paths(argv[2]);
conf::set_contract_dir_paths(argv[0], argv[2]);
return 0;
}
@@ -114,7 +115,7 @@ void std_terminate() noexcept
int main(int argc, char **argv)
{
//seed rand
srand(time(0));
srand(time(0));
// Register exception handler for std exceptions.
std::set_terminate(&std_terminate);
@@ -172,6 +173,8 @@ int main(int argc, char **argv)
if (p2p::init() != 0 || usr::init() != 0 || cons::init() != 0)
return -1;
statefs::init(conf::ctx.statehistdir);
// After initializing primary subsystems, register the SIGINT handler.
signal(SIGINT, signal_handler);

View File

@@ -4,6 +4,9 @@
#include "../fbschema/common_helpers.hpp"
#include "../fbschema/p2pmsg_container_generated.h"
#include "../fbschema/p2pmsg_content_generated.h"
#include "../statefs/hasher.hpp"
#include "../statefs/state_common.hpp"
#include "../statefs/hashtree_builder.hpp"
#include "proc.hpp"
namespace proc
@@ -34,6 +37,9 @@ std::vector<int> hpscfds;
// Holds the contract process id (if currently executing).
pid_t contract_pid;
// Holds the state monitor process id (if currently executing).
pid_t statemon_pid;
/**
* Executes the contract process and passes the specified arguments.
* @return 0 on successful process creation. -1 on failure or contract process is already running.
@@ -48,6 +54,10 @@ int exec_contract(const contract_exec_args &args)
if (feed_inputs(args) != 0)
return -1;
// Start the state monitor before starting the contract process.
if (start_state_monitor() != 0)
return -1;
const pid_t pid = fork();
if (pid > 0)
{
@@ -58,7 +68,7 @@ int exec_contract(const contract_exec_args &args)
close_unused_fds(true);
// Wait for child process (contract process) to complete execution.
const int presult = await_contract_execution();
const int presult = await_process_execution(contract_pid);
LOG_INFO << "Contract process ended.";
contract_pid = 0;
@@ -68,6 +78,9 @@ int exec_contract(const contract_exec_args &args)
return -1;
}
if (stop_state_monitor() != 0)
return -1;
// After contract execution, collect contract outputs.
if (fetch_outputs(args) != 0)
return -1;
@@ -92,11 +105,12 @@ int exec_contract(const contract_exec_args &args)
execv_args[conf::cfg.runtime_binexec_args.size()] = NULL;
int ret = execv(execv_args[0], execv_args);
LOG_ERR << "Execv failed: " << ret;
LOG_ERR << "Contract process execv failed: " << ret;
exit(1);
}
else
{
LOG_ERR << "fork() failed.";
LOG_ERR << "fork() failed when starting contract process.";
return -1;
}
@@ -104,21 +118,84 @@ int exec_contract(const contract_exec_args &args)
}
/**
* Blocks the calling thread until the contract process compelted exeution (if running).
* @return 0 if contract process exited normally, exit code of contract process if abnormally exited.
* Blocks the calling thread until the specified process compelted exeution (if running).
* @return 0 if process exited normally or exit code of process if abnormally exited.
*/
int await_contract_execution()
int await_process_execution(pid_t pid)
{
if (contract_pid > 0)
if (pid > 0)
{
int scstatus;
waitpid(contract_pid, &scstatus, 0);
waitpid(pid, &scstatus, 0);
if (!WIFEXITED(scstatus))
return WEXITSTATUS(scstatus);
}
return 0;
}
/**
* Mounts the fuse file system at the contract state dir by starting the state monitor process.
* State monitor will automatically create a state history checkpoint as well.
*/
int start_state_monitor()
{
pid_t pid = fork();
if (pid > 0)
{
// HP process.
statemon_pid = pid;
return 0;
}
else if (pid == 0)
{
// State monitor process.
LOG_DBG << "Starting 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[2] = conf::ctx.statedir.data();
execv_args[3] = NULL;
int ret = execv(execv_args[0], execv_args);
LOG_ERR << "State monitor execv failed: " << ret;
exit(1);
}
else if (pid < 0)
{
LOG_ERR << "fork() failed when starting state monitor.";
return -1;
}
}
/**
* Terminate the state monitor and update the latest state hash tree.
*/
int stop_state_monitor()
{
kill(statemon_pid, SIGINT);
// Wait for state monitor process to complete execution after the SIGINT.
const int presult = await_process_execution(statemon_pid);
LOG_DBG << "State monitor stopped.";
statemon_pid = 0;
if (presult != 0)
LOG_ERR << "State monitor process exited with non-normal status code: " << presult;
// Update the hash tree.
hasher::B2H statehash = {0, 0, 0, 0};
statefs::hashtree_builder htreebuilder(statefs::get_statedir_context());
if (htreebuilder.generate(statehash) != 0)
return -1;
LOG_DBG << "State hash: " << std::hex << statehash << std::dec;
return 0;
}
/**
* Writes the contract args (JSON) into the stdin of the contract process.
* Args format:

View File

@@ -80,7 +80,11 @@ int exec_contract(const contract_exec_args &args);
//------Internal-use functions for this namespace.
int await_contract_execution();
int await_process_execution(pid_t pid);
int start_state_monitor();
int stop_state_monitor();
int write_contract_args(const contract_exec_args &args);

55
src/statefs/hasher.cpp Normal file
View File

@@ -0,0 +1,55 @@
#include "hasher.hpp"
namespace hasher
{
// provide some helper functions for working with 32 byte hash type
bool operator==(const B2H &lhs, const B2H &rhs)
{
return lhs.data[0] == rhs.data[0] && lhs.data[1] == rhs.data[1] && lhs.data[2] == rhs.data[2] && lhs.data[3] == rhs.data[3];
}
bool operator!=(const B2H &lhs, const B2H &rhs)
{
return lhs.data[0] != rhs.data[0] || lhs.data[1] != rhs.data[1] || lhs.data[2] != rhs.data[2] || lhs.data[3] != rhs.data[3];
}
void operator^=(B2H &lhs, const B2H &rhs)
{
lhs.data[0] ^= rhs.data[0];
lhs.data[1] ^= rhs.data[1];
lhs.data[2] ^= rhs.data[2];
lhs.data[3] ^= rhs.data[3];
}
std::ostream &operator<<(std::ostream &output, const B2H &h)
{
output << h.data[0] << h.data[1] << h.data[2] << h.data[3];
return output;
}
std::stringstream &operator<<(std::stringstream &output, const B2H &h)
{
output << std::hex << h;
return output;
}
// the actual hash function, note that the B2H datatype is always passed by value being only 4 quadwords
B2H hash(const void *buf1, size_t buf1len, const void *buf2, size_t buf2len)
{
crypto_generichash_blake2b_state state;
crypto_generichash_blake2b_init(&state, NULL, 0, HASH_SIZE);
crypto_generichash_blake2b_update(&state,
reinterpret_cast<const unsigned char *>(buf1), buf1len);
crypto_generichash_blake2b_update(&state,
reinterpret_cast<const unsigned char *>(buf2), buf2len);
B2H ret;
crypto_generichash_blake2b_final(
&state,
reinterpret_cast<unsigned char *>(&ret),
HASH_SIZE);
return ret;
}
} // namespace hasher

27
src/statefs/hasher.hpp Normal file
View File

@@ -0,0 +1,27 @@
#ifndef _HASHER_
#define _HASHER_
#include "../pchheader.hpp"
namespace hasher
{
constexpr size_t HASH_SIZE = crypto_generichash_blake2b_BYTES;
struct B2H // blake2b hash is 32 bytes which we store as 4 quad words
{
uint64_t data[4];
};
// provide some helper functions for working with 32 byte hash type
bool operator==(const B2H &lhs, const B2H &rhs);
bool operator!=(const B2H &lhs, const B2H &rhs);
void operator^=(B2H &lhs, const B2H &rhs);
std::ostream &operator<<(std::ostream &output, const B2H &h);
std::stringstream &operator<<(std::stringstream &output, const B2H &h);
B2H hash(const void *buf1, size_t buf1len, const void *buf2, size_t buf2len);
} // namespace hasher
#endif

View File

@@ -0,0 +1,334 @@
#include "../pchheader.hpp"
#include "../hplog.hpp"
#include "state_common.hpp"
#include "hashmap_builder.hpp"
#include "hasher.hpp"
namespace statefs
{
hashmap_builder::hashmap_builder(const statedir_context &ctx) : ctx(ctx)
{
}
int hashmap_builder::generate_hashmap_forfile(hasher::B2H &parentdirhash, const std::string &filepath)
{
// 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
// delta block index (.bindex) file must exist.
// If the block index exists, we generate/update the hashmap file with the aid of that.
// Block index file contains the updated blockids. If not, we simply rehash all the blocks.
std::string relpath = get_relpath(filepath, ctx.datadir);
// Open the actual data file and calculate the block count.
int orifd = open(filepath.data(), O_RDONLY);
if (orifd == -1)
{
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);
// Attempt to read the existing block hash map file.
std::string bhmapfile;
std::vector<char> bhmapdata;
if (read_blockhashmap(bhmapdata, bhmapfile, relpath) == -1)
return -1;
hasher::B2H oldfilehash = {0, 0, 0, 0};
if (!bhmapdata.empty())
memcpy(&oldfilehash, bhmapdata.data(), hasher::HASH_SIZE);
// 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, relpath) == -1)
return -1;
// 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;
if (update_hashes(hashes.get(), hashes_size, relpath, orifd, blockcount, original_blockcount, bindex, bhmapdata) == -1)
return -1;
if (write_blockhashmap(bhmapfile, hashes.get(), hashes_size) == -1)
return -1;
if (update_hashtree_entry(parentdirhash, !bhmapdata.empty(), oldfilehash, hashes[0], bhmapfile, relpath) == -1)
return -1;
return 0;
}
int hashmap_builder::read_blockhashmap(std::vector<char> &bhmapdata, std::string &bhmapfile, const std::string &relpath)
{
bhmapfile.reserve(ctx.blockhashmapdir.length() + relpath.length() + HASHMAP_EXT_LEN);
bhmapfile.append(ctx.blockhashmapdir).append(relpath).append(HASHMAP_EXT);
if (boost::filesystem::exists(bhmapfile))
{
int hmapfd = open(bhmapfile.c_str(), O_RDONLY);
if (hmapfd == -1)
{
LOG_ERR << errno << ": Open failed " << bhmapfile;
return -1;
}
off_t size = lseek(hmapfd, 0, SEEK_END);
bhmapdata.resize(size);
if (pread(hmapfd, bhmapdata.data(), size, 0) == -1)
{
LOG_ERR << errno << ": Read failed " << bhmapfile;
return -1;
}
}
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();
if (created_bhmapsubdirs.count(hmapsubdir.string()) == 0)
{
boost::filesystem::create_directories(hmapsubdir);
created_bhmapsubdirs.emplace(hmapsubdir.string());
}
}
return 0;
}
int hashmap_builder::get_blockindex(std::map<uint32_t, hasher::B2H> &idxmap, uint32_t &totalblockcount, const std::string &filerelpath)
{
std::string bindexfile;
bindexfile.reserve(ctx.deltadir.length() + filerelpath.length() + BLOCKINDEX_EXT_LEN);
bindexfile.append(ctx.deltadir).append(filerelpath).append(BLOCKINDEX_EXT);
if (boost::filesystem::exists(bindexfile))
{
std::ifstream infile(bindexfile, std::ios::binary | std::ios::ate);
std::streamsize idxsize = infile.tellg();
infile.seekg(0, std::ios::beg);
// Read the block index file into a vector.
std::vector<char> bindex(idxsize);
if (infile.read(bindex.data(), idxsize))
{
// First 8 bytes contain the original file length.
off_t orifilelen;
memcpy(&orifilelen, bindex.data(), 8);
totalblockcount = ceil((double)orifilelen / (double)BLOCK_SIZE);
// Skip the first 8 bytes and loop through index entries.
for (uint32_t idxoffset = 8; idxoffset < 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)
// Read the block hash (32 bytes).
hasher::B2H hash;
memcpy(&hash, bindex.data() + idxoffset, 32);
idxoffset += 32;
idxmap.try_emplace(blockno, hash);
}
}
else
{
LOG_ERR << errno << ": Read failed " << bindexfile;
return -1;
}
infile.close();
}
return 0;
}
int hashmap_builder::update_hashes(
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)
{
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())
{
// Load old hashes.
memcpy(hashes, bhmapdata.data(), hashes_size < bhmapdata.size() ? hashes_size : bhmapdata.size());
// Refer to the block index and rehash the changed blocks.
for (const auto [blockid, oldhash] : bindex)
{
// If the blockid 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)
break;
if (compute_blockhash(hashes[blockid + 1], blockid, 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;
else
nohint_blockstart = blockcount; // 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++)
{
if (compute_blockhash(hashes[blockid + 1], blockid, orifd, relpath) == -1)
return -1;
}
// Calculate the new file hash: filehash = HASH(filename + XOR(block hashes))
hasher::B2H filehash{0, 0, 0, 0};
for (int i = 1; i <= blockcount; i++)
filehash ^= hashes[i];
// Rehash the file hash with filename included.
const std::string filename = boost::filesystem::path(relpath.data()).filename().string();
filehash = hasher::hash(filename.c_str(), filename.length(), &filehash, hasher::HASH_SIZE);
hashes[0] = filehash;
return 0;
}
int hashmap_builder::compute_blockhash(hasher::B2H &hash, uint32_t blockid, 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)
{
LOG_ERR << errno << ": Read failed " << relpath;
return -1;
}
hash = hasher::hash(&blockoffset, 8, blockbuf.get(), bytesread);
return 0;
}
int hashmap_builder::write_blockhashmap(const std::string &bhmapfile, const hasher::B2H *hashes, const off_t hashes_size)
{
int hmapfd = open(bhmapfile.c_str(), O_RDWR | O_TRUNC | O_CREAT, FILE_PERMS);
if (hmapfd == -1)
{
LOG_ERR << errno << ": Open failed " << bhmapfile;
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;
return -1;
}
if (ftruncate(hmapfd, hashes_size) == -1)
{
LOG_ERR << errno << ": Truncate failed " << bhmapfile;
return -1;
}
}
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)
{
std::string hardlinkdir(ctx.hashtreedir);
const std::string relpathdir = boost::filesystem::path(relpath).parent_path().string();
hardlinkdir.append(relpathdir);
if (relpathdir != "/")
hardlinkdir.append("/");
std::stringstream newhlpath;
newhlpath << hardlinkdir << newfilehash << ".rh";
if (oldbhmap_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)
return -1;
// Subtract the old root hash and add the new root hash from the parent dir hash.
parentdirhash ^= oldfilehash;
parentdirhash ^= newfilehash;
}
else
{
// Create a new hard link with new root hash as the name.
if (link(bhmapfile.c_str(), newhlpath.str().c_str()) == -1)
return -1;
// Add the new root hash to parent hash.
parentdirhash ^= newfilehash;
}
return 0;
}
int hashmap_builder::remove_hashmapfile(hasher::B2H &parentdirhash, const std::string &bhmapfile)
{
if (boost::filesystem::exists(bhmapfile))
{
int hmapfd = open(bhmapfile.data(), O_RDONLY);
if (hmapfd == -1)
{
LOG_ERR << errno << ": Open failed " << bhmapfile;
return -1;
}
hasher::B2H filehash;
if (read(hmapfd, &filehash, hasher::HASH_SIZE) == -1)
{
LOG_ERR << errno << ": Read failed " << bhmapfile;
return -1;
}
// Delete the .bhmap file.
if (remove(bhmapfile.c_str()) == -1)
{
LOG_ERR << errno << ": Delete failed " << bhmapfile;
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();
hardlinkdir.append(relpathdir);
if (relpathdir != "/")
hardlinkdir.append("/");
std::stringstream hlpath;
hlpath << hardlinkdir << filehash << ".rh";
if (remove(hlpath.str().c_str()) == -1)
{
LOG_ERR << errno << ": Delete failed for halrd link " << filehash << " of " << bhmapfile;
return -1;
}
// XOR parent dir hash with file hash so the file hash gets removed from parent dir hash.
parentdirhash ^= filehash;
}
return 0;
}
} // namespace statefs

View File

@@ -0,0 +1,35 @@
#ifndef _STATEFS_HASHMAP_BUILDER_
#define _STATEFS_HASHMAP_BUILDER_
#include "../pchheader.hpp"
#include "hasher.hpp"
#include "state_common.hpp"
namespace statefs
{
class hashmap_builder
{
private:
const statedir_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(
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 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);
public:
hashmap_builder(const statedir_context &ctx);
int generate_hashmap_forfile(hasher::B2H &parentdirhash, const std::string &filepath);
int remove_hashmapfile(hasher::B2H &parentdirhash, const std::string &filepath);
};
} // namespace statefs
#endif

View File

@@ -0,0 +1,246 @@
#include "../pchheader.hpp"
#include "hashtree_builder.hpp"
#include "state_restore.hpp"
#include "state_common.hpp"
namespace statefs
{
hashtree_builder::hashtree_builder(const statedir_context &ctx) : ctx(ctx), hmapbuilder(ctx)
{
}
int hashtree_builder::generate(hasher::B2H &roothash)
{
// Load modified file path hints if available.
populate_hintpaths(IDX_TOUCHEDFILES);
populate_hintpaths(IDX_NEWFILES);
hintmode = !hintpaths.empty();
traversel_rootdir = ctx.datadir;
removal_mode = false;
if (update_hashtree(roothash) != 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())
{
traversel_rootdir = ctx.blockhashmapdir;
removal_mode = true;
if (update_hashtree(roothash) != 0)
return -1;
}
return 0;
}
int hashtree_builder::update_hashtree(hasher::B2H &roothash)
{
hintpath_map::iterator hintdir_itr = hintpaths.end();
if (!should_process_dir(hintdir_itr, traversel_rootdir))
return 0;
if (update_hashtree_fordir(roothash, traversel_rootdir, hintdir_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)
{
const std::string htreedirpath = switch_basepath(dirpath, traversel_rootdir, ctx.hashtreedir);
// Load current dir hash if exist.
const std::string dirhashfile = htreedirpath + "/" + DIRHASH_FNAME;
hasher::B2H dirhash = get_existingdirhash(dirhashfile);
// Remember the dir hash before we mutate it.
hasher::B2H original_dirhash = dirhash;
// Iterate files/subdirs inside this dir.
const boost::filesystem::directory_iterator itrend;
for (boost::filesystem::directory_iterator itr(dirpath); itr != itrend; itr++)
{
const bool isdir = boost::filesystem::is_directory(itr->path());
const std::string pathstr = itr->path().string();
if (isdir)
{
hintpath_map::iterator hintsubdir_itr = hintpaths.end();
if (!should_process_dir(hintsubdir_itr, pathstr))
continue;
if (update_hashtree_fordir(dirhash, pathstr, hintsubdir_itr, false) != 0)
return -1;
}
else
{
if (!should_process_file(hintdir_itr, pathstr))
continue;
if (process_file(dirhash, pathstr, htreedirpath) != 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);
// 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)
{
boost::filesystem::remove_all(dirpath);
boost::filesystem::remove_all(htreedirpath);
}
else
{
boost::filesystem::remove(dirhashfile);
}
// Subtract the original dir hash from the parent dir hash.
parentdirhash ^= original_dirhash;
}
else if (dirhash != original_dirhash)
{
// If dir hash has changed, write it back to dir hash file.
if (save_dirhash(dirhashfile, dirhash) == -1)
return -1;
// Also update the parent dir hash by subtracting the old hash and adding the new hash.
parentdirhash ^= original_dirhash;
parentdirhash ^= dirhash;
}
return 0;
}
hasher::B2H hashtree_builder::get_existingdirhash(const std::string &dirhashfile)
{
// Load current dir hash if exist.
hasher::B2H dirhash{0, 0, 0, 0};
int dirhashfd = open(dirhashfile.c_str(), O_RDONLY);
if (dirhashfd > 0)
{
read(dirhashfd, &dirhash, hasher::HASH_SIZE);
close(dirhashfd);
}
return dirhash;
}
int hashtree_builder::save_dirhash(const std::string &dirhashfile, hasher::B2H dirhash)
{
int dirhashfd = open(dirhashfile.c_str(), O_RDWR | O_TRUNC | O_CREAT, FILE_PERMS);
if (dirhashfd == -1)
return -1;
if (write(dirhashfd, &dirhash, hasher::HASH_SIZE) == -1)
{
close(dirhashfd);
return -1;
}
close(dirhashfd);
return 0;
}
inline bool hashtree_builder::should_process_dir(hintpath_map::iterator &dir_itr, const std::string &dirpath)
{
return (hintmode ? get_hinteddir_match(dir_itr, dirpath) : true);
}
bool hashtree_builder::should_process_file(const hintpath_map::iterator hintdir_itr, const std::string filepath)
{
if (hintmode)
{
if (hintdir_itr == hintpaths.end())
return false;
std::string relpath = get_relpath(filepath, traversel_rootdir);
// 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);
std::unordered_set<std::string> &hintfiles = hintdir_itr->second;
const auto hintfile_itr = hintfiles.find(relpath);
if (hintfile_itr == hintfiles.end())
return false;
// Erase the visiting filepath from hint files.
hintfiles.erase(hintfile_itr);
}
return true;
}
int hashtree_builder::process_file(hasher::B2H &parentdirhash, const std::string &filepath, const std::string &htreedirpath)
{
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)
{
boost::filesystem::create_directories(htreedirpath);
created_htreesubdirs.emplace(htreedirpath);
}
if (hmapbuilder.generate_hashmap_forfile(parentdirhash, filepath) == -1)
return -1;
}
else
{
if (hmapbuilder.remove_hashmapfile(parentdirhash, filepath) == -1)
return -1;
}
return 0;
}
void hashtree_builder::populate_hintpaths(const char *const idxfile)
{
std::ifstream infile(std::string(ctx.deltadir).append(idxfile));
if (!infile.fail())
{
for (std::string relpath; std::getline(infile, relpath);)
{
std::string parentdir = boost::filesystem::path(relpath).parent_path().string();
hintpaths[parentdir].emplace(relpath);
}
infile.close();
}
}
bool hashtree_builder::get_hinteddir_match(hintpath_map::iterator &matchitr, 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);
if (exactmatchitr != hintpaths.end())
{
matchitr = exactmatchitr;
return true;
}
for (auto itr = hintpaths.begin(); itr != hintpaths.end(); itr++)
{
if (strncmp(relpath.c_str(), itr->first.c_str(), relpath.length()) == 0)
{
// Partial match found.
matchitr = hintpaths.end();
return true;
}
}
return false; // Not found at all.
}
} // namespace statefs

View File

@@ -0,0 +1,48 @@
#ifndef _STATEFS_HASHTREE_BUILDER_
#define _STATEFS_HASHTREE_BUILDER_
#include "../pchheader.hpp"
#include "hasher.hpp"
#include "hashmap_builder.hpp"
#include "state_common.hpp"
namespace statefs
{
typedef std::unordered_map<std::string, std::unordered_set<std::string>> hintpath_map;
class hashtree_builder
{
private:
const statedir_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;
bool hintmode;
bool removal_mode;
std::string traversel_rootdir;
// List of new root hash map sub directories created during the session.
std::unordered_set<std::string> created_htreesubdirs;
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);
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(const char *const idxfile);
bool get_hinteddir_match(hintpath_map::iterator &matchitr, const std::string &dirpath);
public:
hashtree_builder(const statedir_context &ctx);
int generate(hasher::B2H &roothash);
};
} // namespace statefs
#endif

View File

@@ -0,0 +1,60 @@
#include <string>
#include <boost/filesystem.hpp>
#include "state_common.hpp"
namespace statefs
{
std::string statehistdir;
void init(const std::string &statehist_dir_root)
{
statehistdir = realpath(statehist_dir_root.c_str(), NULL);
// Initialize 0 state (current state) directory.
get_statedir_context(0, true);
}
std::string get_statedir_root(const int16_t checkpointid)
{
return statehistdir + "/" + std::to_string(checkpointid);
}
statedir_context get_statedir_context(const int16_t checkpointid, const bool createdirs)
{
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;
if (createdirs)
{
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);
}
return ctx;
}
std::string get_relpath(const std::string &fullpath, const std::string &base_path)
{
std::string relpath = fullpath.substr(base_path.length(), fullpath.length() - base_path.length());
if (relpath.empty())
relpath = "/";
return relpath;
}
std::string switch_basepath(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);
}
} // namespace statefs

View File

@@ -0,0 +1,60 @@
#ifndef _STATEFS_STATE_COMMON_
#define _STATEFS_STATE_COMMON_
#include <sys/types.h>
#include <string>
#include "hasher.hpp"
namespace statefs
{
constexpr int16_t MAX_CHECKPOINTS = 5;
// Cache block size.
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 MAX_HASHES = BLOCK_SIZE / hasher::HASH_SIZE;
// 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 BLOCKINDEX_EXT = ".bindex";
constexpr size_t BLOCKINDEX_EXT_LEN = 7;
const char *const BLOCKCACHE_EXT = ".bcache";
constexpr size_t BLOCKCACHE_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 DATA_DIR = "/data";
const char *const BHMAP_DIR = "/bhmap";
const char *const HTREE_DIR = "/htree";
const char *const DELTA_DIR = "/delta";
extern std::string statehistdir;
struct statedir_context
{
std::string rootdir;
std::string datadir;
std::string blockhashmapdir;
std::string hashtreedir;
std::string deltadir;
};
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);
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);
} // namespace statefs
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,9 @@
#ifndef _FUSE_FS_
#define _FUSE_FS_
namespace fusefs
{
int start(const char *arg0, const char *source, const char *mountpoint, const char *deltadir);
}
#endif

View File

@@ -0,0 +1,519 @@
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <limits.h>
#include <unordered_map>
#include <cmath>
#include <boost/filesystem.hpp>
#include <fstream>
#include <sstream>
#include <string>
#include <errno.h>
#include "../hasher.hpp"
#include "../state_common.hpp"
#include "state_monitor.hpp"
namespace statefs
{
void state_monitor::create_checkpoint()
{
// Shift -1 and below checkpoints by 1 more.
// If MAX oldest checkpoint is there, remove it and work our way upwards.
int16_t oldest_chkpnt = (MAX_CHECKPOINTS + 1) * -1; // +1 because we maintain one extra checkpoint in case of rollbacks.
for (int16_t chkpnt = oldest_chkpnt; chkpnt <= -1; chkpnt++)
{
std::string dir = get_statedir_root(chkpnt);
if (boost::filesystem::exists(dir))
{
if (chkpnt == oldest_chkpnt)
{
boost::filesystem::remove_all(dir);
}
else
{
std::string dirshift = get_statedir_root(chkpnt - 1);
boost::filesystem::rename(dir, dirshift);
}
}
if (chkpnt == -1)
{
statedir_context ctx = get_statedir_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);
}
}
return;
}
void state_monitor::oncreate(const int fd)
{
std::lock_guard<std::mutex> lock(monitor_mutex);
std::string filepath;
if (extract_filepath(filepath, fd) == 0)
oncreate_filepath(filepath);
}
void state_monitor::onopen(const int inodefd, const int flags)
{
std::lock_guard<std::mutex> lock(monitor_mutex);
std::string filepath;
if (extract_filepath(filepath, inodefd) == 0)
{
state_file_info *fi;
if (get_tracked_fileinfo(&fi, filepath) == 0)
{
// Check whether fd is open in truncate mode. If so cache the entire file immediately.
if (flags & O_TRUNC)
cache_blocks(*fi, 0, fi->original_length);
}
}
}
void state_monitor::onwrite(const int fd, const off_t offset, const size_t length)
{
std::lock_guard<std::mutex> lock(monitor_mutex);
std::string filepath;
if (get_fd_filepath(filepath, fd) == 0)
{
state_file_info *fi;
if (get_tracked_fileinfo(&fi, filepath) == 0)
cache_blocks(*fi, offset, length);
}
}
void state_monitor::onrename(const std::string &oldfilepath, const std::string &newfilepath)
{
std::lock_guard<std::mutex> lock(monitor_mutex);
ondelete_filepath(oldfilepath);
oncreate_filepath(newfilepath);
}
void state_monitor::ondelete(const std::string &filepath)
{
std::lock_guard<std::mutex> lock(monitor_mutex);
ondelete_filepath(filepath);
}
void state_monitor::ontruncate(const int fd, const off_t newsize)
{
std::lock_guard<std::mutex> lock(monitor_mutex);
std::string filepath;
if (get_fd_filepath(filepath, fd) == 0)
{
// If truncated size is less than the original, cache the entire file.
state_file_info *fi;
if (get_tracked_fileinfo(&fi, filepath) == 0 && newsize < fi->original_length)
cache_blocks(*fi, 0, fi->original_length);
}
}
void state_monitor::onclose(const int fd)
{
std::lock_guard<std::mutex> lock(monitor_mutex);
auto pitr = fdpathmap.find(fd);
if (pitr != fdpathmap.end())
{
// Close any block cache/index fds we have opened for this file.
auto fitr = fileinfomap.find(pitr->second); // pitr->second is the filepath string.
if (fitr != fileinfomap.end())
close_cachingfds(fitr->second); // fitr->second is the fileinfo struct.
fdpathmap.erase(pitr);
}
}
/**
* Extracts the full physical file path for a given fd.
* @param filepath String to assign the extracted file path.
* @param fd The file descriptor to find the filepath.
* @return 0 on successful file path extraction. -1 on failure.
*/
int state_monitor::extract_filepath(std::string &filepath, const int fd)
{
char proclnk[32];
sprintf(proclnk, "/proc/self/fd/%d", fd);
filepath.resize(PATH_MAX);
ssize_t len = readlink(proclnk, filepath.data(), PATH_MAX);
if (len > 0)
{
filepath.resize(len);
return 0;
}
return -1;
}
/**
* Find the full physical file path for a given fd using the fd map.
* @param filepath String to assign the extracted file path.
* @param fd The file descriptor to find the filepath.
* @return 0 on successful file path extraction. -1 on failure.
*/
int state_monitor::get_fd_filepath(std::string &filepath, const int fd)
{
// Return path from the map if found.
const auto itr = fdpathmap.find(fd);
if (itr != fdpathmap.end())
{
filepath = itr->second;
return 0;
}
// Extract the file path and populate the fd-->filepath map.
if (extract_filepath(filepath, fd) == 0)
{
fdpathmap[fd] = filepath;
return 0;
}
return -1;
}
void state_monitor::oncreate_filepath(const std::string &filepath)
{
// Check whether we are already tracking this file path.
// Only way this could happen is deleting an existing file and creating a new file with same name.
if (fileinfomap.count(filepath) == 0)
{
// Add an entry for the new file in the file info map. This information will be used to ignore
// future operations (eg. write/delete) done to this file.
state_file_info fi;
fi.isnew = true;
fi.filepath = filepath;
fileinfomap[filepath] = std::move(fi);
// Add to the list of new files added during this session.
write_newfileentry(filepath);
}
}
void state_monitor::ondelete_filepath(const std::string &filepath)
{
state_file_info *fi;
if (get_tracked_fileinfo(&fi, filepath) == 0)
{
if (fi->isnew)
{
// If this is a new file, just remove from existing index entries.
// No need to cache the file blocks.
remove_newfileentry(fi->filepath);
fileinfomap.erase(filepath);
}
else
{
// If not a new file, cache the entire file.
cache_blocks(*fi, 0, fi->original_length);
}
}
}
/**
* Finds the tracked state file information for the given filepath.
* @param fi Reference pointer to assign the state file info struct.
* @param filepath Full physical path of the file.
* @return 0 on successful find. -1 on failure.
*/
int state_monitor::get_tracked_fileinfo(state_file_info **fi, const std::string &filepath)
{
// Return from filepath-->fileinfo map if found.
const auto itr = fileinfomap.find(filepath);
if (itr != fileinfomap.end())
{
*fi = &itr->second;
return 0;
}
// Initialize a new state file info struct for the given filepath.
state_file_info &fileinfo = fileinfomap[filepath];
// We use stat() to find out the length of the file.
struct stat stat_buf;
if (stat(filepath.c_str(), &stat_buf) != 0)
{
std::cerr << errno << ": Error occured in stat() of " << filepath << "\n";
return -1;
}
fileinfo.original_length = stat_buf.st_size;
fileinfo.filepath = filepath;
*fi = &fileinfo;
return 0;
}
/**
* Caches the specified bytes range of the given file.
* @param fi The file info struct pointing to the file to be cached.
* @param offset The start byte position for caching.
* @param length How many bytes to cache.
* @return 0 on successful execution. -1 on failure.
*/
int state_monitor::cache_blocks(state_file_info &fi, const off_t offset, const size_t length)
{
// No caching required if this is a new file created during this session.
if (fi.isnew)
return 0;
uint32_t original_blockcount = ceil((double)fi.original_length / (double)BLOCK_SIZE);
// Check whether we have already cached the entire file.
if (original_blockcount == fi.cached_blockids.size())
return 0;
// Initialize fds and indexes required for caching.
if (prepare_caching(fi) != 0)
return -1;
// Return if incoming write is outside any of the original blocks.
if (offset > original_blockcount * BLOCK_SIZE)
return 0;
uint32_t startblock = offset / BLOCK_SIZE;
uint32_t endblock = (offset + length) / BLOCK_SIZE;
// std::cout << "Cache blocks: '" << fi.filepath << "' [" << offset << "," << length << "] " << startblock << "," << endblock << "\n";
// If this is the first time we are caching this file, write an entry to the touched file index.
if (fi.cached_blockids.empty() && write_touchedfileentry(fi.filepath) != 0)
return -1;
for (uint32_t i = startblock; i <= endblock; i++)
{
// Skip if we have already cached this block.
if (fi.cached_blockids.count(i) > 0)
continue;
// 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::cerr << errno << ": Read failed " << fi.filepath << "\n";
return -1;
}
// No more bytes to read in this file.
if (bytesread == 0)
return 0;
if (write(fi.cachefd, blockbuf.get(), bytesread) < 0)
{
std::cerr << errno << ": Write to block cache failed. " << fi.filepath << "\n";
return -1;
}
// Append an entry (44 bytes) into the block cache index. We maintain this index to
// help random block access for external use cases. We currently do not sort this index here.
// Whoever is using the index must sort it if required.
// Entry format: [blocknum(4 bytes) | cacheoffset(8 bytes) | blockhash(32 bytes)]
char entrybuf[BLOCKINDEX_ENTRY_SIZE];
hasher::B2H hash = hasher::hash(&blockoffset, 8, blockbuf.get(), bytesread);
// Original file block id.
memcpy(entrybuf, &i, 4);
// Position of the block within the cache file.
off_t cacheoffset = fi.cached_blockids.size() * BLOCK_SIZE;
memcpy(entrybuf + 4, &cacheoffset, 8);
// The block hash.
memcpy(entrybuf + 12, hash.data, 32);
if (write(fi.indexfd, entrybuf, BLOCKINDEX_ENTRY_SIZE) < 0)
{
std::cerr << errno << ": Write to block index failed. " << fi.filepath << "\n";
return -1;
}
// Mark the block as cached.
fi.cached_blockids.emplace(i);
}
return 0;
}
/**
* Initializes fds and indexes required for caching.
* @param fi The state file info struct pointing to the file being cached.
* @return 0 on succesful initialization. -1 on failure.
*/
int state_monitor::prepare_caching(state_file_info &fi)
{
// If readfd is greater than 0 then we take it as caching being already initialized.
if (fi.readfd > 0)
return 0;
// Open up the file using a read-only fd. This fd will be used to fetch blocks to be cached.
fi.readfd = open(fi.filepath.c_str(), O_RDONLY);
if (fi.readfd < 0)
{
std::cerr << errno << ": Open failed " << fi.filepath << "\n";
return -1;
}
// 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 tmppath;
tmppath.reserve(ctx.deltadir.length() + relpath.length() + BLOCKCACHE_EXT_LEN);
tmppath.append(ctx.deltadir).append(relpath).append(BLOCKCACHE_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();
if (created_cachesubdirs.count(cachesubdir.string()) == 0)
{
boost::filesystem::create_directories(cachesubdir);
created_cachesubdirs.emplace(cachesubdir.string());
}
// Create and open the block cache file.
fi.cachefd = open(tmppath.c_str(), O_WRONLY | O_APPEND | O_CREAT, FILE_PERMS);
if (fi.cachefd <= 0)
{
std::cerr << errno << ": Open failed " << tmppath << "\n";
return -1;
}
// Create and open the block index file.
tmppath.replace(tmppath.length() - BLOCKCACHE_EXT_LEN, BLOCKINDEX_EXT_LEN, BLOCKINDEX_EXT);
fi.indexfd = open(tmppath.c_str(), O_WRONLY | O_APPEND | O_CREAT, FILE_PERMS);
if (fi.indexfd <= 0)
{
std::cerr << errno << ": Open failed " << tmppath << "\n";
return -1;
}
// Write first entry (8 bytes) to the index file. First entry is the length of the original file.
// This will be helpful when restoring/rolling back a file.
if (write(fi.indexfd, &fi.original_length, 8) == -1)
{
std::cerr << errno << ": Error writing to index file " << tmppath << "\n";
return -1;
}
return 0;
}
/**
* Closes any open caching fds for a given file.
*/
void state_monitor::close_cachingfds(state_file_info &fi)
{
if (fi.readfd > 0)
close(fi.readfd);
if (fi.cachefd > 0)
close(fi.cachefd);
if (fi.indexfd > 0)
close(fi.indexfd);
fi.readfd = 0;
fi.cachefd = 0;
fi.indexfd = 0;
}
/**
* Inserts a file into the modified files list of this session.
* This index is used to restore modified files during restore.
*/
int state_monitor::write_touchedfileentry(std::string_view filepath)
{
if (touchedfileindexfd <= 0)
{
std::string indexfile = ctx.deltadir + "/idxtouched.idx";
touchedfileindexfd = open(indexfile.c_str(), O_WRONLY | O_APPEND | O_CREAT, FILE_PERMS);
if (touchedfileindexfd <= 0)
{
std::cerr << errno << ": Open failed " << indexfile << "\n";
return -1;
}
}
// Write the relative file path line to the index.
filepath = filepath.substr(ctx.datadir.length(), filepath.length() - ctx.datadir.length());
write(touchedfileindexfd, filepath.data(), filepath.length());
write(touchedfileindexfd, "\n", 1);
return 0;
}
/**
* Inserts a file into the list of new files created during this session.
* This index is used in deleting new files during restore.
*/
int state_monitor::write_newfileentry(std::string_view filepath)
{
std::string indexfile = ctx.deltadir + "/idxnew.idx";
int fd = open(indexfile.c_str(), O_WRONLY | O_APPEND | O_CREAT, FILE_PERMS);
if (fd <= 0)
{
std::cerr << errno << ": Open failed " << indexfile << "\n";
return -1;
}
// Write the relative file path line to the index.
filepath = filepath.substr(ctx.datadir.length(), filepath.length() - ctx.datadir.length());
write(fd, filepath.data(), filepath.length());
write(fd, "\n", 1);
close(fd);
return 0;
}
/**
* Scans and removes the given filepath from the new files index.
*/
void state_monitor::remove_newfileentry(std::string_view filepath)
{
filepath = filepath.substr(ctx.datadir.length(), filepath.length() - ctx.datadir.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 indexfile = ctx.deltadir + "/idxnew.idx";
std::string indexfile_tmp = ctx.deltadir + "/idxnew.idx.tmp";
std::ifstream infile(indexfile);
std::ofstream outfile(indexfile_tmp);
bool linestransferred = false;
for (std::string line; std::getline(infile, line);)
{
if (line != filepath) // Skip the file being removed.
{
outfile << line << "\n";
linestransferred = true;
}
}
infile.close();
outfile.close();
// Remove the old index.
std::remove(indexfile.c_str());
// If no lines transferred, delete the temp file as well.
if (linestransferred)
std::rename(indexfile_tmp.c_str(), indexfile.c_str());
else
std::remove(indexfile_tmp.c_str());
}
} // namespace statefs

View File

@@ -0,0 +1,77 @@
#ifndef _STATEFS_STATE_MONITOR_
#define _STATEFS_STATE_MONITOR_
#include <cstdint>
#include <sys/types.h>
#include <unordered_map>
#include <unordered_set>
#include <mutex>
#include <boost/filesystem.hpp>
#include "../state_common.hpp"
namespace statefs
{
// Holds information about an original file in state that we are tracking.
struct state_file_info
{
bool isnew;
off_t original_length;
std::unordered_set<uint32_t> cached_blockids;
std::string filepath;
int readfd;
int cachefd;
int indexfd;
};
// Invoked by fuse file system for relevent file system calls.
class state_monitor
{
private:
// Map of fd-->filepath
std::unordered_map<int, std::string> fdpathmap;
// Map of filepath-->fileinfo
std::unordered_map<std::string, state_file_info> fileinfomap;
// Complete list of modified files during the session.
std::unordered_set<std::string> touchedfiles;
// List of new cache sub directories created during the session.
std::unordered_set<std::string> created_cachesubdirs;
// Mutex to synchronize parallel file system calls into our custom state tracking logic.
std::mutex monitor_mutex;
// Holds the fd used to write into modified files index. This will be kept open for the entire
// life of the state monitor.
int touchedfileindexfd = 0;
int extract_filepath(std::string &filepath, const int fd);
int get_fd_filepath(std::string &filepath, const int fd);
void oncreate_filepath(const std::string &filepath);
void ondelete_filepath(const std::string &filepath);
int get_tracked_fileinfo(state_file_info **fileinfo, const std::string &filepath);
int cache_blocks(state_file_info &fi, const off_t offset, const size_t length);
int prepare_caching(state_file_info &fi);
void close_cachingfds(state_file_info &fi);
int write_touchedfileentry(std::string_view filepath);
int write_newfileentry(std::string_view filepath);
void remove_newfileentry(std::string_view filepath);
public:
statedir_context ctx;
void create_checkpoint();
void oncreate(const int fd);
void onopen(const int inodefd, const int flags);
void onwrite(const int fd, const off_t offset, const size_t length);
void onrename(const std::string &oldfilepath, const std::string &newfilepath);
void ondelete(const std::string &filepath);
void ontruncate(const int fd, const off_t newsize);
void onclose(const int fd);
};
} // namespace statefs
#endif

View File

@@ -0,0 +1,200 @@
#include "../pchheader.hpp"
#include "../hplog.hpp"
#include "hasher.hpp"
#include "state_restore.hpp"
#include "hashtree_builder.hpp"
#include "state_common.hpp"
namespace statefs
{
// Look at new files added and delete them if still exist.
void state_restore::delete_newfiles()
{
std::string indexfile(ctx.deltadir);
indexfile.append(IDX_NEWFILES);
std::ifstream infile(indexfile);
for (std::string file; std::getline(infile, file);)
{
std::string filepath(ctx.datadir);
filepath.append(file);
std::remove(filepath.c_str());
}
infile.close();
}
// Look at touched files and restore them.
int state_restore::restore_touchedfiles()
{
std::unordered_set<std::string> processed;
std::string indexfile(ctx.deltadir);
indexfile.append(IDX_TOUCHEDFILES);
std::ifstream infile(indexfile);
for (std::string file; std::getline(infile, file);)
{
// Skip if already processed.
if (processed.count(file) > 0)
continue;
std::vector<char> bindex;
if (read_blockindex(bindex, file) != 0)
return -1;
if (restore_blocks(file, bindex) != 0)
return -1;
// Add to processed file list.
processed.emplace(file);
}
infile.close();
return 0;
}
// Read the delta block index.
int state_restore::read_blockindex(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);
buffer.resize(idxsize);
if (!infile.read(buffer.data(), idxsize))
{
LOG_ERR << errno << ": Read failed " << bindexfile;
return -1;
}
return 0;
}
// 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();
// First 8 bytes of the index contains the supposed length of the original file.
off_t originallen = 0;
memcpy(&originallen, idxptr, 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)
{
LOG_ERR << errno << ": Open failed " << bcachefile;
return -1;
}
}
// Create or Open original file.
{
std::string originalfile(ctx.datadir);
originalfile.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();
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)
{
LOG_ERR << errno << ": Open failed " << originalfile;
return -1;
}
}
// Restore the blocks as specified in block index.
for (uint32_t idxoffset = 8; idxoffset < 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;
// 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)
// Transfer the cached block to the target file.
copy_file_range(bcachefd, &bcacheoffset, orifilefd, &orifileoffset, 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);
close(bcachefd);
close(orifilefd);
return 0;
}
// This is called after a rollback so the all checkpoint dirs shift by 1.
void state_restore::rewind_checkpoints()
{
// Assuming we have restored the current state with current delta,
// we need to shift each history delta by 1 place.
// Delete the state 0 (current) delta.
boost::filesystem::remove_all(ctx.deltadir);
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);
if (boost::filesystem::exists(dir))
{
if (chkpnt == -1)
{
// 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::remove_all(dir);
}
else
{
std::string dirshift = get_statedir_root(chkpnt + 1);
boost::filesystem::rename(dir, dirshift);
}
}
}
}
// Rolls back current state to previous state.
int state_restore::rollback(hasher::B2H &roothash)
{
ctx = get_statedir_context();
delete_newfiles();
if (restore_touchedfiles() == -1)
return -1;
// Update hash tree.
hashtree_builder htreebuilder(ctx);
htreebuilder.generate(roothash);
rewind_checkpoints();
return 0;
}
} // namespace statefs

View File

@@ -0,0 +1,28 @@
#ifndef _STATEFS_STATE_RESTORE_
#define _STATEFS_STATE_RESTORE_
#include "../pchheader.hpp"
#include "hasher.hpp"
#include "state_common.hpp"
namespace statefs
{
class state_restore
{
private:
statedir_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);
int restore_blocks(std::string_view file, const std::vector<char> &bindex);
void rewind_checkpoints();
public:
int rollback(hasher::B2H &roothash);
};
} // namespace statefs
#endif

View File

@@ -188,4 +188,13 @@ std::string_view getsv(const rapidjson::Value &v)
return std::string_view(v.GetString(), v.GetStringLength());
}
// provide a safe std::string overload for realpath
std::string realpath(std::string path)
{
std::array<char, PATH_MAX> buffer;
::realpath(path.c_str(), buffer.data());
buffer[PATH_MAX] = '\0';
return buffer.data();
}
} // namespace util

View File

@@ -11,10 +11,10 @@ namespace util
{
// Hot Pocket version. Displayed on 'hotpocket version' and written to new contract configs.
constexpr const char* HP_VERSION = "0.1";
constexpr const char *HP_VERSION = "0.1";
// Minimum compatible contract config version (this will be used to validate contract configs)
constexpr const char* MIN_CONTRACT_VERSION = "0.1";
constexpr const char *MIN_CONTRACT_VERSION = "0.1";
// Current version of the peer message protocol.
constexpr uint8_t PEERMSG_VERSION = 1;
@@ -74,6 +74,8 @@ int version_compare(const std::string &x, const std::string &y);
std::string_view getsv(const rapidjson::Value &v);
std::string realpath(std::string path);
} // namespace util
#endif