mirror of
https://github.com/tstack/lnav.git
synced 2024-09-11 13:05:51 +03:00
[arc] report errors when opening archives
This commit is contained in:
parent
563fa94d39
commit
e4ea9ca584
@ -390,6 +390,10 @@ add_library(diag STATIC
|
||||
base/result.h
|
||||
styling.hh
|
||||
ring_span.hh
|
||||
safe/accessmode.h
|
||||
safe/defaulttypes.h
|
||||
safe/mutableref.h
|
||||
safe/safe.h
|
||||
sequence_sink.hh
|
||||
shlex.hh
|
||||
simdutf8check.h
|
||||
|
@ -322,6 +322,10 @@ noinst_HEADERS = \
|
||||
regexp_vtab.hh \
|
||||
relative_time.hh \
|
||||
ring_span.hh \
|
||||
safe/accessmode.h \
|
||||
safe/defaulttypes.h \
|
||||
safe/mutableref.h \
|
||||
safe/safe.h \
|
||||
sequence_matcher.hh \
|
||||
sequence_sink.hh \
|
||||
session_data.hh \
|
||||
|
@ -134,10 +134,12 @@ filename_to_tmp_path(const std::string &filename)
|
||||
}
|
||||
|
||||
#if HAVE_ARCHIVE_H
|
||||
static int
|
||||
copy_data(const ghc::filesystem::path &path,
|
||||
static walk_result_t
|
||||
copy_data(const std::string& filename,
|
||||
struct archive *ar,
|
||||
struct archive_entry *entry,
|
||||
struct archive *aw,
|
||||
const ghc::filesystem::path &entry_path,
|
||||
struct extract_progress *ep)
|
||||
{
|
||||
int r;
|
||||
@ -148,32 +150,38 @@ copy_data(const ghc::filesystem::path &path,
|
||||
for (;;) {
|
||||
r = archive_read_data_block(ar, &buff, &size, &offset);
|
||||
if (r == ARCHIVE_EOF) {
|
||||
return (ARCHIVE_OK);
|
||||
return Ok();
|
||||
}
|
||||
if (r < ARCHIVE_OK) {
|
||||
return (r);
|
||||
if (r != ARCHIVE_OK) {
|
||||
return Err(fmt::format("failed to read file: {} >> {} -- {}",
|
||||
filename,
|
||||
archive_entry_pathname_utf8(entry),
|
||||
archive_error_string(ar)));
|
||||
}
|
||||
r = archive_write_data_block(aw, buff, size, offset);
|
||||
if (r < ARCHIVE_OK) {
|
||||
log_error("%s", archive_error_string(aw));
|
||||
return (r);
|
||||
if (r != ARCHIVE_OK) {
|
||||
return Err(fmt::format("failed to write file: {} -- {}",
|
||||
entry_path.string(),
|
||||
archive_error_string(aw)));
|
||||
}
|
||||
|
||||
total += size;
|
||||
ep->ep_out_size.fetch_add(size);
|
||||
|
||||
if ((total - last_space_check) > (1024 * 1024)) {
|
||||
auto tmp_space = ghc::filesystem::space(path);
|
||||
auto tmp_space = ghc::filesystem::space(entry_path);
|
||||
|
||||
if (tmp_space.available < MIN_FREE_SPACE) {
|
||||
log_error("available space too low: %lld", tmp_space.available);
|
||||
return ARCHIVE_FATAL;
|
||||
return Err(fmt::format(
|
||||
"{} -- available space too low: %lld",
|
||||
entry_path.string(),
|
||||
tmp_space.available));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void extract(const std::string &filename, const extract_cb &cb)
|
||||
static walk_result_t extract(const std::string &filename, const extract_cb &cb)
|
||||
{
|
||||
static int FLAGS = ARCHIVE_EXTRACT_TIME
|
||||
| ARCHIVE_EXTRACT_PERM
|
||||
@ -191,7 +199,7 @@ static void extract(const std::string &filename, const extract_cb &cb)
|
||||
ghc::filesystem::last_write_time(
|
||||
done_path, std::chrono::system_clock::now());
|
||||
log_debug("already extracted! %s", done_path.c_str());
|
||||
return;
|
||||
return Ok();
|
||||
}
|
||||
|
||||
auto_mem<archive> arc(archive_free);
|
||||
@ -204,7 +212,9 @@ static void extract(const std::string &filename, const extract_cb &cb)
|
||||
archive_write_disk_set_options(ext, FLAGS);
|
||||
archive_write_disk_set_standard_lookup(ext);
|
||||
if (archive_read_open_filename(arc, filename.c_str(), 10240) != ARCHIVE_OK) {
|
||||
return;
|
||||
return Err(fmt::format("unable to open archive: {} -- {}",
|
||||
filename,
|
||||
archive_error_string(arc)));
|
||||
}
|
||||
|
||||
log_info("extracting %s to %s",
|
||||
@ -217,11 +227,10 @@ static void extract(const std::string &filename, const extract_cb &cb)
|
||||
log_info("all done");
|
||||
break;
|
||||
}
|
||||
if (r < ARCHIVE_OK) {
|
||||
log_error("%s", archive_error_string(arc));
|
||||
}
|
||||
if (r < ARCHIVE_WARN) {
|
||||
return;
|
||||
if (r != ARCHIVE_OK) {
|
||||
return Err(fmt::format("unable to read entry header: {} -- {}",
|
||||
filename,
|
||||
archive_error_string(arc)));
|
||||
}
|
||||
|
||||
auto_mem<archive_entry> wentry(archive_entry_free);
|
||||
@ -237,23 +246,18 @@ static void extract(const std::string &filename, const extract_cb &cb)
|
||||
wentry, S_IRUSR | (S_ISDIR(entry_mode) ? S_IXUSR|S_IWUSR : 0));
|
||||
r = archive_write_header(ext, wentry);
|
||||
if (r < ARCHIVE_OK) {
|
||||
log_error("%s", archive_error_string(ext));
|
||||
return Err(fmt::format("unable to write entry: {} -- {}",
|
||||
entry_path.string(),
|
||||
archive_error_string(ext)));
|
||||
}
|
||||
else if (archive_entry_size(entry) > 0) {
|
||||
r = copy_data(tmp_path, arc, ext, prog);
|
||||
if (r < ARCHIVE_OK) {
|
||||
log_error("%s", archive_error_string(ext));
|
||||
}
|
||||
if (r < ARCHIVE_WARN) {
|
||||
return;
|
||||
}
|
||||
TRY(copy_data(filename, arc, entry, ext, entry_path, prog));
|
||||
}
|
||||
r = archive_write_finish_entry(ext);
|
||||
if (r < ARCHIVE_OK) {
|
||||
log_error("%s", archive_error_string(ext));
|
||||
}
|
||||
if (r < ARCHIVE_WARN) {
|
||||
return;
|
||||
if (r != ARCHIVE_OK) {
|
||||
return Err(fmt::format("unable to finish entry: {} -- {}",
|
||||
entry_path.string(),
|
||||
archive_error_string(ext)));
|
||||
}
|
||||
}
|
||||
archive_read_close(arc);
|
||||
@ -261,21 +265,21 @@ static void extract(const std::string &filename, const extract_cb &cb)
|
||||
|
||||
auto_fd(open(done_path.c_str(), O_CREAT | O_WRONLY, 0600));
|
||||
|
||||
// TODO return errors
|
||||
return Ok();
|
||||
}
|
||||
#endif
|
||||
|
||||
void walk_archive_files(const std::string &filename,
|
||||
const extract_cb &cb,
|
||||
const std::function<void(
|
||||
const fs::path&,
|
||||
const fs::directory_entry &)>& callback)
|
||||
walk_result_t walk_archive_files(
|
||||
const std::string &filename,
|
||||
const extract_cb &cb,
|
||||
const std::function<void(
|
||||
const fs::path&,
|
||||
const fs::directory_entry &)>& callback)
|
||||
{
|
||||
#if HAVE_ARCHIVE_H
|
||||
|
||||
auto tmp_path = filename_to_tmp_path(filename);
|
||||
|
||||
extract(filename, cb);
|
||||
TRY(extract(filename, cb));
|
||||
|
||||
for (const auto& entry : fs::recursive_directory_iterator(tmp_path)) {
|
||||
if (!entry.is_regular_file()) {
|
||||
@ -284,6 +288,10 @@ void walk_archive_files(const std::string &filename,
|
||||
|
||||
callback(tmp_path, entry);
|
||||
}
|
||||
|
||||
return Ok();
|
||||
#else
|
||||
return Err("not compiled with libarchive");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -37,6 +37,7 @@
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
|
||||
#include "base/result.h"
|
||||
#include "ghc/filesystem.hpp"
|
||||
|
||||
namespace archive_manager {
|
||||
@ -59,11 +60,14 @@ bool is_archive(const std::string &filename);
|
||||
|
||||
ghc::filesystem::path filename_to_tmp_path(const std::string &filename);
|
||||
|
||||
void walk_archive_files(const std::string &filename,
|
||||
const extract_cb &cb,
|
||||
const std::function<void(
|
||||
const ghc::filesystem::path &,
|
||||
const ghc::filesystem::directory_entry &)> &);
|
||||
using walk_result_t = Result<void, std::string>;
|
||||
|
||||
walk_result_t walk_archive_files(
|
||||
const std::string &filename,
|
||||
const extract_cb &cb,
|
||||
const std::function<void(
|
||||
const ghc::filesystem::path &,
|
||||
const ghc::filesystem::directory_entry &)> &);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -629,16 +629,22 @@ struct Storage<void, E> {
|
||||
}
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
template<typename U,
|
||||
typename = std::enable_if_t<!std::is_same<U, void>::value>>
|
||||
const U& get() const {
|
||||
return *reinterpret_cast<const U *>(&storage_);
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
U& get() {
|
||||
template<typename U,
|
||||
typename = std::enable_if_t<!std::is_same<U, void>::value>>
|
||||
typename std::add_lvalue_reference<U>::type get() {
|
||||
return *reinterpret_cast<U *>(&storage_);
|
||||
}
|
||||
|
||||
template<typename U,
|
||||
typename = std::enable_if_t<std::is_same<U, void>::value>>
|
||||
void get() {}
|
||||
|
||||
type storage_;
|
||||
bool initialized_;
|
||||
};
|
||||
|
@ -258,8 +258,8 @@ files_overlay_source::list_value_for_overlay(const listview_curses &lv, int y,
|
||||
{
|
||||
if (y == 0) {
|
||||
auto &fc = lnav_data.ld_active_files;
|
||||
auto &sp = fc.fc_progress;
|
||||
std::lock_guard<std::mutex> guard(sp->sp_mutex);
|
||||
auto &fc_prog = fc.fc_progress;
|
||||
safe::WriteAccess<safe_scan_progress> sp(*fc_prog);
|
||||
|
||||
if (!sp->sp_extractions.empty()) {
|
||||
static char PROG[] = "-\\|/";
|
||||
|
122
src/lnav.cc
122
src/lnav.cc
@ -578,15 +578,9 @@ void rebuild_indexes()
|
||||
|
||||
if ((!lf->exists() || lf->is_closed())) {
|
||||
log_info("closed log file: %s", lf->get_filename().c_str());
|
||||
if (!lf->is_valid_filename()) {
|
||||
lnav_data.ld_active_files.fc_file_names.erase(lf->get_filename());
|
||||
}
|
||||
lnav_data.ld_text_source.remove(lf);
|
||||
lnav_data.ld_log_source.remove_file(lf);
|
||||
file_iter = lnav_data.ld_active_files.fc_files.erase(file_iter);
|
||||
lnav_data.ld_active_files.fc_files_generation += 1;
|
||||
|
||||
lnav_data.ld_active_files.regenerate_unique_file_names();
|
||||
lnav_data.ld_active_files.close_file(lf);
|
||||
}
|
||||
else {
|
||||
++file_iter;
|
||||
@ -964,9 +958,16 @@ struct same_file {
|
||||
const struct stat &sf_stat;
|
||||
};
|
||||
|
||||
static std::mutex REALPATH_CACHE_MUTEX;
|
||||
static std::unordered_map<std::string, std::string> REALPATH_CACHE;
|
||||
|
||||
void file_collection::close_file(const std::shared_ptr<logfile> &lf)
|
||||
{
|
||||
if (!lf->is_valid_filename()) {
|
||||
if (lf->is_valid_filename()) {
|
||||
std::lock_guard<std::mutex> lg(REALPATH_CACHE_MUTEX);
|
||||
|
||||
REALPATH_CACHE.erase(lf->get_filename());
|
||||
} else {
|
||||
this->fc_file_names.erase(lf->get_filename());
|
||||
}
|
||||
auto file_iter = find(this->fc_files.begin(),
|
||||
@ -1092,10 +1093,16 @@ file_collection::watch_logfile(const string& filename, logfile_open_options &loo
|
||||
if (this->fc_other_files.find(filename) != this->fc_other_files.end()) {
|
||||
return make_ready_future(retval);
|
||||
}
|
||||
return std::async(std::launch::async, [filename, &loo, prog=this->fc_progress]() {
|
||||
file_format_t ff = detect_file_format(filename);
|
||||
return std::async(std::launch::async, [filename, &loo, prog=this->fc_progress, errs=this->fc_name_to_errors]() {
|
||||
file_collection retval;
|
||||
|
||||
if (errs.find(filename) != errs.end()) {
|
||||
// The file is broken, no reason to try and reopen
|
||||
return retval;
|
||||
}
|
||||
|
||||
file_format_t ff = detect_file_format(filename);
|
||||
|
||||
switch (ff) {
|
||||
case file_format_t::FF_SQLITE_DB:
|
||||
attach_sqlite_db(lnav_data.ld_db.in(), filename);
|
||||
@ -1103,15 +1110,23 @@ file_collection::watch_logfile(const string& filename, logfile_open_options &loo
|
||||
break;
|
||||
|
||||
case file_format_t::FF_ARCHIVE: {
|
||||
retval.fc_other_files[filename] = "Archive";
|
||||
archive_manager::walk_archive_files(
|
||||
filename,
|
||||
[prog](const auto& path, const auto total) {
|
||||
std::lock_guard<std::mutex> guard(prog->sp_mutex);
|
||||
prog->sp_extractions.clear();
|
||||
prog->sp_extractions.emplace_back(path, total);
|
||||
nonstd::optional<std::list<archive_manager::extract_progress>::iterator>
|
||||
prog_iter_opt;
|
||||
|
||||
return &prog->sp_extractions.back();
|
||||
auto res = archive_manager::walk_archive_files(
|
||||
filename,
|
||||
[prog, &prog_iter_opt](
|
||||
const auto& path, const auto total) {
|
||||
safe::WriteAccess<safe_scan_progress> sp(*prog);
|
||||
|
||||
prog_iter_opt | [&sp](auto prog_iter) {
|
||||
sp->sp_extractions.erase(prog_iter);
|
||||
};
|
||||
auto prog_iter = sp->sp_extractions.emplace(
|
||||
sp->sp_extractions.begin(), path, total);
|
||||
prog_iter_opt = prog_iter;
|
||||
|
||||
return &(*prog_iter);
|
||||
},
|
||||
[&filename, &retval](const auto& tmp_path,
|
||||
const auto& entry) {
|
||||
@ -1134,7 +1149,6 @@ file_collection::watch_logfile(const string& filename, logfile_open_options &loo
|
||||
log_info("adding file from archive: %s/%s",
|
||||
filename.c_str(),
|
||||
entry.path().c_str());
|
||||
// TODO add some heuristics for hiding files
|
||||
retval.fc_file_names[entry.path().string()] =
|
||||
logfile_open_options()
|
||||
.with_filename(custom_name.string())
|
||||
@ -1142,9 +1156,18 @@ file_collection::watch_logfile(const string& filename, logfile_open_options &loo
|
||||
.with_non_utf_visibility(false)
|
||||
.with_visible_size_limit(128 * 1024);
|
||||
});
|
||||
if (res.isErr()) {
|
||||
log_error("archive extraction failed: %s",
|
||||
res.unwrapErr().c_str());
|
||||
retval.clear();
|
||||
retval.fc_name_to_errors[filename] = res.unwrapErr();
|
||||
} else {
|
||||
retval.fc_other_files[filename] = "Archive";
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(prog->sp_mutex);
|
||||
prog->sp_extractions.clear();
|
||||
prog_iter_opt | [&prog](auto prog_iter) {
|
||||
prog->writeAccess()->sp_extractions.erase(prog_iter);
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -1189,18 +1212,25 @@ file_collection::watch_logfile(const string& filename, logfile_open_options &loo
|
||||
* @param path The glob pattern to expand.
|
||||
* @param required Passed to watch_logfile.
|
||||
*/
|
||||
file_collection file_collection::expand_filename(const string& path, logfile_open_options &loo, bool required)
|
||||
void file_collection::expand_filename(future_queue<file_collection> &fq,
|
||||
const string& path,
|
||||
logfile_open_options &loo,
|
||||
bool required)
|
||||
{
|
||||
static_root_mem<glob_t, globfree> gl;
|
||||
file_collection retval;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lg(REALPATH_CACHE_MUTEX);
|
||||
|
||||
if (REALPATH_CACHE.find(path) != REALPATH_CACHE.end()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_url(path.c_str())) {
|
||||
return retval;
|
||||
return;
|
||||
}
|
||||
else if (glob(path.c_str(), GLOB_NOCHECK, nullptr, gl.inout()) == 0) {
|
||||
future_queue<file_collection> fq([&retval](auto& fc) {
|
||||
retval.merge(fc);
|
||||
});
|
||||
int lpc;
|
||||
|
||||
if (gl->gl_pathc == 1 /*&& gl.gl_matchc == 0*/) {
|
||||
@ -1216,22 +1246,34 @@ file_collection file_collection::expand_filename(const string& path, logfile_ope
|
||||
strcmp(path.c_str(), gl->gl_pathv[0]) != 0) {
|
||||
required = false;
|
||||
}
|
||||
for (lpc = 0; lpc < (int)gl->gl_pathc; lpc++) {
|
||||
auto_mem<char> abspath;
|
||||
|
||||
if ((abspath = realpath(gl->gl_pathv[lpc], nullptr)) == nullptr) {
|
||||
if (required) {
|
||||
fprintf(stderr, "Cannot find file: %s -- %s",
|
||||
gl->gl_pathv[lpc], strerror(errno));
|
||||
std::lock_guard<std::mutex> lg(REALPATH_CACHE_MUTEX);
|
||||
for (lpc = 0; lpc < (int)gl->gl_pathc; lpc++) {
|
||||
auto path_str = std::string(gl->gl_pathv[lpc]);
|
||||
auto iter = REALPATH_CACHE.find(path_str);
|
||||
|
||||
if (iter == REALPATH_CACHE.end()) {
|
||||
auto_mem<char> abspath;
|
||||
|
||||
if ((abspath = realpath(gl->gl_pathv[lpc], nullptr)) ==
|
||||
nullptr) {
|
||||
if (required) {
|
||||
fprintf(stderr, "Cannot find file: %s -- %s",
|
||||
gl->gl_pathv[lpc], strerror(errno));
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
auto p = REALPATH_CACHE.emplace(path_str, abspath);
|
||||
|
||||
iter = p.first;
|
||||
}
|
||||
}
|
||||
else if (required || access(abspath.in(), R_OK) == 0) {
|
||||
fq.push_back(watch_logfile(abspath.in(), loo, required));
|
||||
|
||||
if (required || access(iter->second.c_str(), R_OK) == 0) {
|
||||
fq.push_back(watch_logfile(iter->second, loo, required));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
file_collection file_collection::rescan_files(bool required)
|
||||
@ -1243,11 +1285,11 @@ file_collection file_collection::rescan_files(bool required)
|
||||
|
||||
for (auto& pair : this->fc_file_names) {
|
||||
if (pair.second.loo_fd == -1) {
|
||||
retval.merge(this->expand_filename(pair.first, pair.second, required));
|
||||
this->expand_filename(fq, pair.first, pair.second, required);
|
||||
if (lnav_data.ld_flags & LNF_ROTATED) {
|
||||
string path = pair.first + ".*";
|
||||
|
||||
retval.merge(this->expand_filename(path, pair.second, false));
|
||||
this->expand_filename(fq, path, pair.second, false);
|
||||
}
|
||||
} else {
|
||||
fq.push_back(watch_logfile(pair.first, pair.second, required));
|
||||
@ -2939,7 +2981,7 @@ int main(int argc, char *argv[])
|
||||
if (!lnav_data.ld_active_files.fc_name_to_errors.empty()) {
|
||||
for (const auto& pair : lnav_data.ld_active_files.fc_name_to_errors) {
|
||||
fprintf(stderr,
|
||||
"error: unable to read file: %s -- %s\n",
|
||||
"error: unable to open file: %s -- %s\n",
|
||||
pair.first.c_str(),
|
||||
pair.second.c_str());
|
||||
}
|
||||
|
12
src/lnav.hh
12
src/lnav.hh
@ -43,6 +43,8 @@
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
|
||||
#include "base/future_util.hh"
|
||||
#include "safe/safe.h"
|
||||
#include "logfile.hh"
|
||||
#include "hist_source.hh"
|
||||
#include "statusview_curses.hh"
|
||||
@ -212,10 +214,11 @@ struct key_repeat_history {
|
||||
};
|
||||
|
||||
struct scan_progress {
|
||||
std::mutex sp_mutex;
|
||||
std::list<archive_manager::extract_progress> sp_extractions;
|
||||
};
|
||||
|
||||
using safe_scan_progress = safe::Safe<scan_progress>;
|
||||
|
||||
struct file_collection {
|
||||
std::map<std::string, std::string> fc_name_to_errors;
|
||||
std::map<std::string, logfile_open_options> fc_file_names;
|
||||
@ -225,10 +228,11 @@ struct file_collection {
|
||||
fc_renamed_files;
|
||||
std::set<std::string> fc_closed_files;
|
||||
std::map<std::string, std::string> fc_other_files;
|
||||
std::shared_ptr<scan_progress> fc_progress;
|
||||
std::shared_ptr<safe_scan_progress> fc_progress;
|
||||
size_t fc_largest_path_length{0};
|
||||
|
||||
file_collection() : fc_progress(std::make_shared<scan_progress>()) {}
|
||||
file_collection()
|
||||
: fc_progress(std::make_shared<safe::Safe<scan_progress>>()) {}
|
||||
|
||||
void clear() {
|
||||
this->fc_name_to_errors.clear();
|
||||
@ -238,7 +242,7 @@ struct file_collection {
|
||||
this->fc_other_files.clear();
|
||||
}
|
||||
file_collection rescan_files(bool required = false);
|
||||
file_collection expand_filename(const std::string& path, logfile_open_options &loo, bool required);
|
||||
void expand_filename(future_queue<file_collection> &fq, const std::string& path, logfile_open_options &loo, bool required);
|
||||
std::future<file_collection>
|
||||
watch_logfile(const std::string& filename, logfile_open_options &loo, bool required);
|
||||
void merge(const file_collection &other);
|
||||
|
49
src/safe/accessmode.h
Normal file
49
src/safe/accessmode.h
Normal file
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* @file accessmode.h
|
||||
* @author L.-C. C.
|
||||
* @brief
|
||||
* @version 0.1
|
||||
* @date 2019-01-20
|
||||
*
|
||||
* @copyright Copyright (c) 2019
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#if __cplusplus >= 201402L
|
||||
#include <shared_mutex>
|
||||
#endif // __cplusplus >= 201402L
|
||||
|
||||
namespace safe
|
||||
{
|
||||
enum class AccessMode
|
||||
{
|
||||
ReadOnly,
|
||||
ReadWrite
|
||||
};
|
||||
|
||||
template<typename LockType>
|
||||
struct AccessTraits
|
||||
{
|
||||
static constexpr bool IsReadOnly = false;
|
||||
};
|
||||
template<typename MutexType>
|
||||
struct AccessTraits<std::lock_guard<MutexType>>
|
||||
{
|
||||
static constexpr bool IsReadOnly = false;
|
||||
};
|
||||
template<typename MutexType>
|
||||
struct AccessTraits<std::unique_lock<MutexType>>
|
||||
{
|
||||
static constexpr bool IsReadOnly = false;
|
||||
};
|
||||
#if __cplusplus >= 201402L
|
||||
template<typename MutexType>
|
||||
struct AccessTraits<std::shared_lock<MutexType>>
|
||||
{
|
||||
static constexpr bool IsReadOnly = true;
|
||||
};
|
||||
#endif // __cplusplus >= 201402L
|
||||
} // namespace safe
|
23
src/safe/defaulttypes.h
Normal file
23
src/safe/defaulttypes.h
Normal file
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @file defaulttypes.h
|
||||
* @author L.-C. C.
|
||||
* @brief
|
||||
* @version 0.1
|
||||
* @date 2020-01-29
|
||||
*
|
||||
* @copyright Copyright (c) 2020
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
|
||||
namespace safe
|
||||
{
|
||||
using DefaultMutex = std::mutex;
|
||||
template<typename MutexType>
|
||||
using DefaultReadOnlyLock = std::lock_guard<MutexType>;
|
||||
template<typename MutexType>
|
||||
using DefaultReadWriteLock = std::lock_guard<MutexType>;
|
||||
} // namespace safe
|
40
src/safe/mutableref.h
Normal file
40
src/safe/mutableref.h
Normal file
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* @file mutableref.h
|
||||
* @author L.-C. C.
|
||||
* @brief
|
||||
* @version 0.1
|
||||
* @date 2020-01-03
|
||||
*
|
||||
* @copyright Copyright (c) 2020
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace safe
|
||||
{
|
||||
namespace impl
|
||||
{
|
||||
/**
|
||||
* @brief A helper class that defines a member variable of type
|
||||
* Type. The variable is defined "mutable Type" if Type is not a
|
||||
* reference, the variable is "Type&" if Type is a reference.
|
||||
*
|
||||
* @tparam Type The type of the variable to define.
|
||||
*/
|
||||
template<typename Type>
|
||||
struct MutableIfNotReference
|
||||
{
|
||||
/// Mutable Type object.
|
||||
mutable Type get;
|
||||
};
|
||||
template<typename Type>
|
||||
struct MutableIfNotReference<Type&>
|
||||
{
|
||||
/// Reference to a Type object.
|
||||
Type& get;
|
||||
};
|
||||
} // namespace impl
|
||||
} // namespace safe
|
359
src/safe/safe.h
Normal file
359
src/safe/safe.h
Normal file
@ -0,0 +1,359 @@
|
||||
/**
|
||||
* @file safe.h
|
||||
* @author L.-C. C.
|
||||
* @brief
|
||||
* @version 0.1
|
||||
* @date 2018-09-21
|
||||
*
|
||||
* @copyright Copyright (c) 2018
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "accessmode.h"
|
||||
#include "defaulttypes.h"
|
||||
#include "mutableref.h"
|
||||
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#if __cplusplus >= 201703L
|
||||
#define EXPLICIT_IF_CPP17 explicit
|
||||
#define EXPLICITLY_CONSTRUCT_RETURN_TYPE_IF_CPP17 ReturnType
|
||||
#else
|
||||
#define EXPLICIT_IF_CPP17
|
||||
#define EXPLICITLY_CONSTRUCT_RETURN_TYPE_IF_CPP17
|
||||
#endif
|
||||
|
||||
namespace safe
|
||||
{
|
||||
/**
|
||||
* @brief Use this tag to default construct the mutex when constructing a
|
||||
* Safe object.
|
||||
*/
|
||||
struct DefaultConstructMutex {};
|
||||
static constexpr DefaultConstructMutex default_construct_mutex;
|
||||
|
||||
/**
|
||||
* @brief Wraps a value together with a mutex.
|
||||
*
|
||||
* @tparam ValueType The type of the value to protect.
|
||||
* @tparam MutexType The type of the mutex.
|
||||
*/
|
||||
template<typename ValueType, typename MutexType = DefaultMutex>
|
||||
class Safe
|
||||
{
|
||||
private:
|
||||
/// Type ValueType with reference removed, if present
|
||||
using RemoveRefValueType = typename std::remove_reference<ValueType>::type;
|
||||
/// Type MutexType with reference removed, if present
|
||||
using RemoveRefMutexType = typename std::remove_reference<MutexType>::type;
|
||||
|
||||
/**
|
||||
* @brief Manages a mutex and gives pointer-like access to a value
|
||||
* object.
|
||||
*
|
||||
* @tparam LockType The type of the lock object that manages the
|
||||
* mutex, example: std::lock_guard.
|
||||
* @tparam Mode Determines the access mode of the Access
|
||||
* object. Can be either AccessMode::ReadOnly or
|
||||
* AccessMode::ReadWrite.
|
||||
*/
|
||||
template<template<typename> class LockType, AccessMode Mode>
|
||||
class Access
|
||||
{
|
||||
// Make sure AccessMode is ReadOnly if a read-only lock is used
|
||||
static_assert(!(AccessTraits<LockType<RemoveRefMutexType>>::IsReadOnly && Mode==AccessMode::ReadWrite), "Cannot have ReadWrite access mode with ReadOnly lock. Check the value of AccessTraits<LockType>::IsReadOnly if it exists.");
|
||||
|
||||
/// ValueType with const qualifier if AccessMode is ReadOnly.
|
||||
using ConstIfReadOnlyValueType = typename std::conditional<Mode==AccessMode::ReadOnly, const RemoveRefValueType, RemoveRefValueType>::type;
|
||||
|
||||
public:
|
||||
/// Pointer-to-const ValueType
|
||||
using ConstPointerType = const ConstIfReadOnlyValueType*;
|
||||
/// Pointer-to-const ValueType if Mode is ReadOnly, pointer to ValueType otherwise.
|
||||
using PointerType = ConstIfReadOnlyValueType*;
|
||||
/// Reference-to-const ValueType
|
||||
using ConstReferenceType = const ConstIfReadOnlyValueType&;
|
||||
/// Reference-to-const ValueType if Mode is ReadOnly, reference to ValueType otherwise.
|
||||
using ReferenceType = ConstIfReadOnlyValueType&;
|
||||
|
||||
/**
|
||||
* @brief Construct an Access object from a possibly const
|
||||
* reference to the value object and any additionnal argument
|
||||
* needed to construct the Lock object.
|
||||
*
|
||||
* @tparam LockArgs Deduced from lockArgs.
|
||||
* @param value Reference to the value.
|
||||
* @param lockArgs Arguments needed to construct the lock object.
|
||||
*/
|
||||
template<typename... OtherLockArgs>
|
||||
EXPLICIT_IF_CPP17
|
||||
Access(ReferenceType value, MutexType& mutex, OtherLockArgs&&... otherLockArgs):
|
||||
lock(mutex, std::forward<OtherLockArgs>(otherLockArgs)...),
|
||||
m_value(value)
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Construct a read-only Access object from a const
|
||||
* safe::Safe object and any additionnal argument needed to
|
||||
* construct the Lock object.
|
||||
*
|
||||
* If needed, you can provide additionnal arguments to construct
|
||||
* the lock object (such as std::adopt_lock). The mutex from the
|
||||
* safe::Locakble object is already passed to the lock object's
|
||||
* constructor though, you must not provide it.
|
||||
*
|
||||
* @tparam OtherLockArgs Deduced from otherLockArgs.
|
||||
* @param safe The const Safe object to give protected access to.
|
||||
* @param otherLockArgs Other arguments needed to construct the lock
|
||||
* object.
|
||||
*/
|
||||
template<typename... OtherLockArgs>
|
||||
EXPLICIT_IF_CPP17
|
||||
Access(const Safe& safe, OtherLockArgs&&... otherLockArgs):
|
||||
Access(safe.m_value, safe.m_mutex.get, std::forward<OtherLockArgs>(otherLockArgs)...)
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Construct a read-write Access object from a
|
||||
* safe::Safe object and any additionnal argument needed to
|
||||
* construct the Lock object.
|
||||
*
|
||||
* If needed, you can provide additionnal arguments to construct
|
||||
* the lock object (such as std::adopt_lock). The mutex from the
|
||||
* safe object is already passed to the lock object's constructor
|
||||
* though, you must not provide it.
|
||||
*
|
||||
* @tparam OtherLockArgs Deduced from otherLockArgs.
|
||||
* @param safe The Safe object to give protected access to.
|
||||
* @param otherLockArgs Other arguments needed to construct the lock
|
||||
* object.
|
||||
*/
|
||||
template<typename... OtherLockArgs>
|
||||
EXPLICIT_IF_CPP17
|
||||
Access(Safe& safe, OtherLockArgs&&... otherLockArgs):
|
||||
Access(safe.m_value, safe.m_mutex.get, std::forward<OtherLockArgs>(otherLockArgs)...)
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Construct an Access object from another one.
|
||||
* OtherLockType must implement release() like std::unique_lock
|
||||
* does.
|
||||
*
|
||||
* @tparam OtherLockType Deduced from otherAccess.
|
||||
* @tparam OtherMode Deduced from otherAccess.
|
||||
* @tparam OtherLockArgs Deduced from otherLockArgs.
|
||||
* @param otherAccess The Access object to construct from.
|
||||
* @param otherLockArgs Other arguments needed to construct the lock
|
||||
* object.
|
||||
*/
|
||||
template<template<typename> class OtherLockType, AccessMode OtherMode, typename... OtherLockArgs>
|
||||
EXPLICIT_IF_CPP17
|
||||
Access(Access<OtherLockType, OtherMode>& otherAccess, OtherLockArgs&&... otherLockArgs):
|
||||
Access(*otherAccess, *otherAccess.lock.release(), std::adopt_lock, std::forward<OtherLockArgs>(otherLockArgs)...)
|
||||
{
|
||||
static_assert(OtherMode == AccessMode::ReadWrite || OtherMode == Mode, "Cannot construct a ReadWrite Access object from a ReadOnly one!");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Const accessor to the value.
|
||||
* @return ConstPointerType Const pointer to the protected value.
|
||||
*/
|
||||
ConstPointerType operator->() const noexcept
|
||||
{
|
||||
return &m_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Accessor to the value.
|
||||
* @return ValuePointerType Pointer to the protected value.
|
||||
*/
|
||||
PointerType operator->() noexcept
|
||||
{
|
||||
return &m_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Const accessor to the value.
|
||||
* @return ConstValueReferenceType Const reference to the protected
|
||||
* value.
|
||||
*/
|
||||
ConstReferenceType operator*() const noexcept
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Accessor to the value.
|
||||
* @return ValueReferenceType Reference to the protected.
|
||||
*/
|
||||
ReferenceType operator*() noexcept
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
/// The lock that manages the mutex.
|
||||
mutable LockType<RemoveRefMutexType> lock;
|
||||
|
||||
private:
|
||||
/// The protected value.
|
||||
ReferenceType m_value;
|
||||
};
|
||||
|
||||
/// Reference-to-const ValueType.
|
||||
using ConstValueReferenceType = const RemoveRefValueType&;
|
||||
/// Reference to ValueType.
|
||||
using ValueReferenceType = RemoveRefValueType&;
|
||||
/// Reference to MutexType.
|
||||
using MutexReferenceType = RemoveRefMutexType&;
|
||||
|
||||
public:
|
||||
/// Aliases to ReadAccess and WriteAccess classes for this Safe class.
|
||||
template<template<typename> class LockType=DefaultReadOnlyLock>
|
||||
using ReadAccess = Access<LockType, AccessMode::ReadOnly>;
|
||||
template<template<typename> class LockType=DefaultReadWriteLock>
|
||||
using WriteAccess = Access<LockType, AccessMode::ReadWrite>;
|
||||
|
||||
/**
|
||||
* @brief Construct a Safe object
|
||||
*/
|
||||
Safe() = default;
|
||||
|
||||
/**
|
||||
* @brief Construct a Safe object with default construction of
|
||||
* the mutex and perfect forwarding of the other arguments to
|
||||
* construct the value object.
|
||||
*
|
||||
* @tparam ValueArgs Deduced from valueArgs.
|
||||
* @param valueArgs Perfect forwarding arguments to construct the value object.
|
||||
* @param tag Indicates that the mutex should be default constructed.
|
||||
*/
|
||||
template<typename... ValueArgs>
|
||||
explicit Safe(DefaultConstructMutex, ValueArgs&&... valueArgs):
|
||||
m_mutex(),
|
||||
m_value(std::forward<ValueArgs>(valueArgs)...)
|
||||
{}
|
||||
/**
|
||||
* @brief Construct a Safe object, forwarding the first
|
||||
* argument to construct the mutex and the other arguments to
|
||||
* construct the value object.
|
||||
*
|
||||
* @tparam MutexArg Deduced from mutexArg.
|
||||
* @tparam ValueArgs Deduced from valueArgs.
|
||||
* @param valueArgs Perfect forwarding arguments to construct the
|
||||
* value object.
|
||||
* @param mutexArg Perfect forwarding argument to construct the
|
||||
* mutex object.
|
||||
*/
|
||||
template<typename MutexArg, typename... ValueArgs>
|
||||
explicit Safe(MutexArg&& mutexArg, ValueArgs&&... valueArgs):
|
||||
m_mutex{std::forward<MutexArg>(mutexArg)},
|
||||
m_value(std::forward<ValueArgs>(valueArgs)...)
|
||||
{}
|
||||
|
||||
/// Delete all copy/move construction/assignment, as these operations
|
||||
/// require locking the mutex under the covers.
|
||||
/// Use copy(), assign() and other defined constructors to get the behavior
|
||||
/// you need with an explicit syntax.
|
||||
Safe(const Safe&) = delete;
|
||||
Safe(Safe&&) = delete;
|
||||
Safe& operator =(const Safe&) = delete;
|
||||
Safe& operator =(Safe&&) = delete;
|
||||
|
||||
template<template<typename> class LockType=DefaultReadOnlyLock, typename... LockArgs>
|
||||
ReadAccess<LockType> readAccess(LockArgs&&... lockArgs) const
|
||||
{
|
||||
// using ReturnType = ReadAccess<LockType>;
|
||||
return EXPLICITLY_CONSTRUCT_RETURN_TYPE_IF_CPP17{*this, std::forward<LockArgs>(lockArgs)...};
|
||||
}
|
||||
|
||||
template<template<typename> class LockType=DefaultReadWriteLock, typename... LockArgs>
|
||||
WriteAccess<LockType> writeAccess(LockArgs&&... lockArgs)
|
||||
{
|
||||
// using ReturnType = WriteAccess<LockType>;
|
||||
return EXPLICITLY_CONSTRUCT_RETURN_TYPE_IF_CPP17{*this, std::forward<LockArgs>(lockArgs)...};
|
||||
}
|
||||
|
||||
template<template<typename> class LockType=DefaultReadOnlyLock, typename... LockArgs>
|
||||
RemoveRefValueType copy(LockArgs&&... lockArgs) const
|
||||
{
|
||||
return *readAccess<LockType>(std::forward<LockArgs>(lockArgs)...);
|
||||
}
|
||||
|
||||
template<template<typename> class LockType=DefaultReadWriteLock, typename... LockArgs>
|
||||
void assign(ConstValueReferenceType value, LockArgs&&... lockArgs)
|
||||
{
|
||||
*writeAccess<LockType>(std::forward<LockArgs>(lockArgs)...) = value;
|
||||
}
|
||||
template<template<typename> class LockType=DefaultReadWriteLock, typename... LockArgs>
|
||||
void assign(RemoveRefValueType&& value, LockArgs&&... lockArgs)
|
||||
{
|
||||
*writeAccess<LockType>(std::forward<LockArgs>(lockArgs)...) = std::move(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Unsafe const accessor to the value. If you use this
|
||||
* function, you exit the realm of safe!
|
||||
*
|
||||
* @return ConstValueReferenceType Const reference to the value
|
||||
* object.
|
||||
*/
|
||||
ConstValueReferenceType unsafe() const noexcept
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
/**
|
||||
* @brief Unsafe accessor to the value. If you use this function,
|
||||
* you exit the realm of safe!
|
||||
*
|
||||
* @return ValueReferenceType Reference to the value object.
|
||||
*/
|
||||
ValueReferenceType unsafe() noexcept
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Accessor to the mutex.
|
||||
*
|
||||
* @return MutexReferenceType Reference to the mutex.
|
||||
*/
|
||||
MutexReferenceType mutex() const noexcept
|
||||
{
|
||||
return m_mutex.get;
|
||||
}
|
||||
|
||||
private:
|
||||
/// The helper object that holds the mutable mutex, or a reference to a mutex.
|
||||
impl::MutableIfNotReference<MutexType> m_mutex;
|
||||
/// The value to protect.
|
||||
ValueType m_value;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Type alias for read-only Access.
|
||||
*
|
||||
* @tparam SafeType The type of Safe object to give read-only access to.
|
||||
* @tparam LockType=DefaultReadOnlyLock The type of lock.
|
||||
*/
|
||||
template<
|
||||
typename SafeType,
|
||||
template<typename> class LockType=DefaultReadOnlyLock>
|
||||
using ReadAccess = typename SafeType::template ReadAccess<LockType>;
|
||||
|
||||
/**
|
||||
* @brief Type alias for read-write Access.
|
||||
*
|
||||
* @tparam SafeType The type of Safe object to give read-write access to.
|
||||
* @tparam LockType=DefaultReadWriteLock The type of lock.
|
||||
*/
|
||||
template<
|
||||
typename SafeType,
|
||||
template<typename> class LockType=DefaultReadWriteLock>
|
||||
using WriteAccess = typename SafeType::template WriteAccess<LockType>;
|
||||
} // namespace safe
|
||||
|
||||
#undef EXPLICIT_IF_CPP17
|
||||
#undef EXPLICITLY_CONSTRUCT_RETURN_TYPE_IF_CPP17
|
@ -374,6 +374,7 @@ DISTCLEANFILES = \
|
||||
distclean-local:
|
||||
$(RM_V)rm -rf sessions
|
||||
$(RM_V)rm -rf tmp
|
||||
$(RM_V)rm -rf rotmp
|
||||
$(RM_V)rm -rf meta-sessions
|
||||
$(RM_V)rm -rf test-config
|
||||
$(RM_V)rm -rf .lnav
|
||||
|
@ -15,7 +15,9 @@ EOF
|
||||
fi
|
||||
|
||||
if test x"${LIBARCHIVE_LIBS}" != x""; then
|
||||
(cd ${srcdir} && tar cfz ${builddir}/test-logs.tgz logfile_access_log.* logfile_empty.0)
|
||||
(cd ${srcdir} && tar cfz ${builddir}/test-logs.tgz logfile_access_log.* logfile_empty.0 -C ${builddir} ../src/lnav)
|
||||
|
||||
dd if=test-logs.tgz of=test-logs-trunc.tgz bs=4096 count=20
|
||||
|
||||
mkdir -p tmp
|
||||
run_test env TMPDIR=tmp ${lnav_test} -n test-logs.tgz
|
||||
@ -46,6 +48,29 @@ EOF
|
||||
logfile_access_log.0 1
|
||||
logfile_access_log.1 1
|
||||
logfile_empty.0 0
|
||||
EOF
|
||||
|
||||
run_test env TMPDIR=tmp ${lnav_test} -n \
|
||||
test-logs-trunc.tgz
|
||||
|
||||
sed -e "s|${builddir}||g" `test_err_filename` | head -1 \
|
||||
> test_logfile.trunc.out
|
||||
mv test_logfile.trunc.out `test_err_filename`
|
||||
check_error_output "truncated tgz not reported correctly" <<EOF
|
||||
error: unable to open file: /test-logs-trunc.tgz -- failed to read file: /test-logs-trunc.tgz >> ../src/lnav -- truncated gzip input
|
||||
EOF
|
||||
|
||||
mkdir -p rotmp
|
||||
chmod ugo-w rotmp
|
||||
run_test env TMPDIR=rotmp ${lnav_test} -n test-logs.tgz
|
||||
|
||||
sed -e "s|lnav-[0-9]*-archives|lnav-NNN-archives|g" \
|
||||
-e "s|${builddir}||g" \
|
||||
`test_err_filename` | head -1 \
|
||||
> test_logfile.rotmp.out
|
||||
mv test_logfile.rotmp.out `test_err_filename`
|
||||
check_error_output "archive not unpacked" <<EOF
|
||||
error: unable to open file: /test-logs.tgz -- unable to write entry: rotmp/lnav-NNN-archives/test-logs.tgz/logfile_access_log.0 -- Failed to create dir 'rotmp/lnav-NNN-archives'
|
||||
EOF
|
||||
fi
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user