[piper] add metadata to piper file header and cleanup workdir

pull/1179/head
Tim Stack 11 months ago
parent 1f5849e430
commit 401ec5181f

@ -34,13 +34,18 @@ Features:
* Added `config get` and `config blame` management CLI * Added `config get` and `config blame` management CLI
commands to get the current configuration and the file commands to get the current configuration and the file
locations where the configuration options came from. locations where the configuration options came from.
Bug Fixes:
* When piping data into **lnav**'s stdin, the input used to * When piping data into **lnav**'s stdin, the input used to
only be written to a single file without any rotation. only be written to a single file without any rotation.
Now, the input is written to a directory of rotating files. Now, the input is written to a directory of rotating files.
The same is true for the command-lines executed through the The same is true for the command-lines executed through the
new `:sh` command. new `:sh` command. The piped data can be managed using the
new `piper` commands in the management CLI.
* The `$LNAV_HOME_DIR` and `$LNAV_WORK_DIR` environment
variables are now defined inside **lnav** and refer to
the location of the user's configuration directory and
the directory where cached data is stored, respectively.
Bug Fixes:
* Binary data piped into stdin should now be treated the same * Binary data piped into stdin should now be treated the same
as if it was in a file that was passed on the command-line. as if it was in a file that was passed on the command-line.
* The `-I` option is now recognized in the management CLI * The `-I` option is now recognized in the management CLI
@ -62,6 +67,9 @@ Breaking changes:
* Removed the `-t` command-line flag. Text data fed in * Removed the `-t` command-line flag. Text data fed in
on stdin and captured from a `:sh` execution is on stdin and captured from a `:sh` execution is
automatically timestamped. automatically timestamped.
* Data piped into **lnav** is now stored in the work
directory instead of the `stdin-captures` dot-lnav
directory.
## lnav v0.11.2 ## lnav v0.11.2

@ -55,6 +55,15 @@
"description": "The number of rotated files to keep", "description": "The number of rotated files to keep",
"type": "integer", "type": "integer",
"minimum": 2 "minimum": 2
},
"ttl": {
"title": "/tuning/piper/ttl",
"description": "The time-to-live for captured data, expressed as a duration (e.g. '3d' for three days)",
"type": "string",
"examples": [
"3d",
"12h"
]
} }
}, },
"additionalProperties": false "additionalProperties": false

@ -123,9 +123,14 @@ Subcommands
Print out the configuration options as JSON-Pointers and the Print out the configuration options as JSON-Pointers and the
file/line-number where the configuration is sourced from. file/line-number where the configuration is sourced from.
.. option:: regex101 import <regex101-url> <format-name> [<regex-name>] .. option:: format <format-name> get
Convert a regex101.com entry into a skeleton log format file. Print information about the given log format.
.. option:: format <format-name> source
Print the name of the first file that contained this log format
definition.
.. option:: format <format-name> regex <regex-name> push .. option:: format <format-name> regex <regex-name> push
@ -135,6 +140,22 @@ Subcommands
Pull changes to a regex that was previously pushed to regex101.com . Pull changes to a regex that was previously pushed to regex101.com .
.. option:: piper clean
Remove all of the files that stored data that was piped into **lnav**.
.. option:: piper list
List all of the data that was piped into **lnav** from oldest to newest.
The listing will show the creation time, the URL you can use to reopen
the data, and a description of the data. Passing the :option:`-v`
option will print out additional metadata that was captured, such as
the current working directory of **lnav** and the environment variables.
.. option:: regex101 import <regex101-url> <format-name> [<regex-name>]
Convert a regex101.com entry into a skeleton log format file.
Environment Variables Environment Variables
--------------------- ---------------------

@ -479,6 +479,15 @@ hash are treated as comments. The following variables are defined in a script:
The arguments passed to the script. The arguments passed to the script.
.. envvar:: LNAV_HOME_DIR
The path to the directory where the user's **lnav** configuration is stored.
.. envvar:: LNAV_WORK_DIR
The path to the directory where **lnav** caches files, like archives that
have been unpacked or piper captures.
Remember that you need to use the :ref:`:eval<eval>` command when referencing Remember that you need to use the :ref:`:eval<eval>` command when referencing
variables in most **lnav** commands. Scripts can provide help text to be variables in most **lnav** commands. Scripts can provide help text to be
displayed during interactive usage by adding the following tags in a comment displayed during interactive usage by adding the following tags in a comment

@ -194,7 +194,9 @@ set(BUILTIN_LNAV_SCRIPTS
scripts/docker-url-handler.lnav scripts/docker-url-handler.lnav
scripts/lnav-pop-view.lnav scripts/lnav-pop-view.lnav
scripts/partition-by-boot.lnav scripts/rename-stdin.lnav scripts/partition-by-boot.lnav scripts/rename-stdin.lnav
scripts/search-for.lnav) scripts/search-for.lnav
scripts/workdir-url-handler.lnav
)
set(BUILTIN_LNAV_SCRIPT_PATHS ${BUILTIN_LNAV_SCRIPTS}) set(BUILTIN_LNAV_SCRIPT_PATHS ${BUILTIN_LNAV_SCRIPTS})

@ -19,6 +19,7 @@ add_library(
lnav_log.cc lnav_log.cc
network.tcp.cc network.tcp.cc
paths.cc paths.cc
piper.file.cc
snippet_highlighters.cc snippet_highlighters.cc
string_attr_type.cc string_attr_type.cc
string_util.cc string_util.cc
@ -55,6 +56,7 @@ add_library(
math_util.hh math_util.hh
network.tcp.hh network.tcp.hh
paths.hh paths.hh
piper.file.hh
result.h result.h
snippet_highlighters.hh snippet_highlighters.hh
string_attr_type.hh string_attr_type.hh

@ -55,6 +55,7 @@ noinst_HEADERS = \
network.tcp.hh \ network.tcp.hh \
opt_util.hh \ opt_util.hh \
paths.hh \ paths.hh \
piper.file.hh \
result.h \ result.h \
snippet_highlighters.hh \ snippet_highlighters.hh \
string_attr_type.hh \ string_attr_type.hh \
@ -82,6 +83,7 @@ libbase_a_SOURCES = \
lnav_log.cc \ lnav_log.cc \
network.tcp.cc \ network.tcp.cc \
paths.cc \ paths.cc \
piper.file.cc \
snippet_highlighters.cc \ snippet_highlighters.cc \
string_attr_type.cc \ string_attr_type.cc \
string_util.cc \ string_util.cc \

@ -66,6 +66,16 @@ public:
return retval; return retval;
} }
static auto_mem calloc(size_t count)
{
return auto_mem(static_cast<T*>(::calloc(count, sizeof(T))));
}
static auto_mem malloc(size_t sz)
{
return auto_mem(static_cast<T*>(::malloc(sz)));
}
explicit auto_mem(T* ptr = nullptr) explicit auto_mem(T* ptr = nullptr)
: am_ptr(ptr), am_free_func(default_free) : am_ptr(ptr), am_free_func(default_free)
{ {

@ -434,8 +434,10 @@ struct string_fragment {
}); });
} }
using split_when_result = std::pair<string_fragment, string_fragment>;
template<typename P> template<typename P>
split_result split_when(P&& predicate) const split_when_result split_when(P&& predicate) const
{ {
int consumed = 0; int consumed = 0;
while (consumed < this->length()) { while (consumed < this->length()) {
@ -446,7 +448,33 @@ struct string_fragment {
consumed += 1; consumed += 1;
} }
if (consumed == 0) { return std::make_pair(
string_fragment{
this->sf_string,
this->sf_begin,
this->sf_begin + consumed,
},
string_fragment{
this->sf_string,
this->sf_begin + consumed
+ ((consumed == this->length()) ? 0 : 1),
this->sf_end,
});
}
template<typename P>
split_result split_pair(P&& predicate) const
{
int consumed = 0;
while (consumed < this->length()) {
if (predicate(this->data()[consumed])) {
break;
}
consumed += 1;
}
if (consumed == this->length()) {
return nonstd::nullopt; return nonstd::nullopt;
} }
@ -843,6 +871,12 @@ to_string_fragment(const std::string& s)
return string_fragment(s.c_str(), 0, s.length()); return string_fragment(s.c_str(), 0, s.length());
} }
inline string_fragment
to_string_fragment(const scn::string_view& sv)
{
return string_fragment::from_bytes(sv.data(), sv.length());
}
struct frag_hasher { struct frag_hasher {
size_t operator()(const string_fragment& sf) const size_t operator()(const string_fragment& sf) const
{ {

@ -40,6 +40,19 @@
namespace lnav { namespace lnav {
namespace gzip { namespace gzip {
struct header {
timeval h_mtime{};
auto_buffer h_extra{auto_buffer::alloc(0)};
std::string h_name;
std::string h_comment;
bool empty() const
{
return this->h_mtime.tv_sec == 0 && this->h_extra.empty()
&& this->h_name.empty() && this->h_comment.empty();
}
};
bool is_gzipped(const char* buffer, size_t len); bool is_gzipped(const char* buffer, size_t len);
Result<auto_buffer, std::string> compress(const void* input, size_t len); Result<auto_buffer, std::string> compress(const void* input, size_t len);

@ -0,0 +1,80 @@
/**
* Copyright (c) 2023, Timothy Stack
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Timothy Stack nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "piper.file.hh"
#include <unistd.h>
#include "base/lnav_log.hh"
#include "base/paths.hh"
namespace lnav {
namespace piper {
const char HEADER_MAGIC[4] = {'L', 0, 'N', 1};
const ghc::filesystem::path&
storage_path()
{
static auto INSTANCE = lnav::paths::workdir() / "piper";
return INSTANCE;
}
nonstd::optional<auto_buffer>
read_header(int fd, const char* first8)
{
if (memcmp(first8, HEADER_MAGIC, sizeof(HEADER_MAGIC)) != 0) {
log_trace("first 4 bytes are not a piper header: %02x%02x%02x%02x",
first8[0],
first8[1],
first8[2],
first8[3]);
return nonstd::nullopt;
}
uint32_t meta_size = ntohl(*((uint32_t*) &first8[4]));
auto meta_buf = auto_buffer::alloc(meta_size);
if (meta_buf.in() == nullptr) {
log_error("failed to alloc %d bytes for header", meta_size);
return nonstd::nullopt;
}
auto meta_prc = pread(fd, meta_buf.in(), meta_size, 8);
if (meta_prc != meta_size) {
log_error("failed to read piper header: %s", strerror(errno));
return nonstd::nullopt;
}
meta_buf.resize(meta_size);
return meta_buf;
}
} // namespace piper
} // namespace lnav

@ -0,0 +1,76 @@
/**
* Copyright (c) 2023, Timothy Stack
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Timothy Stack nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef lnav_piper_file_hh
#define lnav_piper_file_hh
#include <map>
#include <string>
#include <sys/time.h>
#include "auto_mem.hh"
#include "ghc/filesystem.hpp"
#include "optional.hpp"
#include "time_util.hh"
namespace lnav {
namespace piper {
struct header {
timeval h_ctime{};
std::string h_name;
std::string h_cwd;
std::map<std::string, std::string> h_env;
bool operator<(const header& rhs) const
{
if (this->h_ctime < rhs.h_ctime) {
return true;
}
if (this->h_ctime == rhs.h_ctime) {
return this->h_name < rhs.h_name;
}
return false;
}
};
const ghc::filesystem::path& storage_path();
constexpr size_t HEADER_SIZE = 8;
extern const char HEADER_MAGIC[4];
nonstd::optional<auto_buffer> read_header(int fd, const char* first8);
} // namespace piper
} // namespace lnav
#endif

@ -36,20 +36,31 @@
#include <unistd.h> #include <unistd.h>
#include "base/fs_util.hh" #include "base/fs_util.hh"
#include "base/injector.hh"
#include "base/paths.hh" #include "base/paths.hh"
#include "config.h" #include "config.h"
#include "line_buffer.hh" #include "line_buffer.hh"
#include "piper.looper.cfg.hh"
namespace file_converter_manager { namespace file_converter_manager {
static const ghc::filesystem::path&
cache_dir()
{
static auto INSTANCE = lnav::paths::workdir() / "conversion";
return INSTANCE;
}
Result<convert_result, std::string> Result<convert_result, std::string>
convert(const external_file_format& eff, const std::string& filename) convert(const external_file_format& eff, const std::string& filename)
{ {
log_info("attempting to convert file -- %s", filename.c_str()); log_info("attempting to convert file -- %s", filename.c_str());
ghc::filesystem::create_directories(lnav::paths::workdir()); ghc::filesystem::create_directories(cache_dir());
auto outfile = TRY(lnav::filesystem::open_temp_file(lnav::paths::workdir() auto outfile = TRY(lnav::filesystem::open_temp_file(
/ "conversion.XXXXXX")); cache_dir()
/ fmt::format(FMT_STRING("{}.XXXXXX"), eff.eff_format_name)));
auto err_pipe = TRY(auto_pipe::for_child_fd(STDERR_FILENO)); auto err_pipe = TRY(auto_pipe::for_child_fd(STDERR_FILENO));
auto child = TRY(lnav::pid::from_fork()); auto child = TRY(lnav::pid::from_fork());
@ -144,4 +155,32 @@ convert(const external_file_format& eff, const std::string& filename)
}); });
} }
void
cleanup()
{
(void) std::async(std::launch::async, []() {
const auto& cfg = injector::get<const lnav::piper::config&>();
auto now = std::chrono::system_clock::now();
auto cache_path = cache_dir();
std::vector<ghc::filesystem::path> to_remove;
for (const auto& entry :
ghc::filesystem::directory_iterator(cache_path))
{
auto mtime = ghc::filesystem::last_write_time(entry.path());
auto exp_time = mtime + cfg.c_ttl;
if (now < exp_time) {
continue;
}
to_remove.emplace_back(entry);
}
for (auto& entry : to_remove) {
log_debug("removing conversion: %s", entry.c_str());
ghc::filesystem::remove_all(entry);
}
});
}
} // namespace file_converter_manager } // namespace file_converter_manager

@ -50,6 +50,8 @@ struct convert_result {
Result<convert_result, std::string> convert(const external_file_format& eff, Result<convert_result, std::string> convert(const external_file_format& eff,
const std::string& filename); const std::string& filename);
void cleanup();
} // namespace file_converter_manager } // namespace file_converter_manager
#endif #endif

@ -115,8 +115,7 @@ CREATE TABLE lnav_file (
sqlite3_result_error(ctx, "file is too large", -1); sqlite3_result_error(ctx, "file is too large", -1);
} else { } else {
auto fd = lf->get_fd(); auto fd = lf->get_fd();
auto_mem<char> buf; auto buf = auto_mem<char>::malloc(lf_stat.st_size);
buf = (char*) malloc(lf_stat.st_size);
auto rc = pread(fd, buf, lf_stat.st_size, 0); auto rc = pread(fd, buf, lf_stat.st_size, 0);
if (rc == -1) { if (rc == -1) {

@ -45,6 +45,7 @@
#include "base/string_util.hh" #include "base/string_util.hh"
#include "config.h" #include "config.h"
#include "lnav_util.hh" #include "lnav_util.hh"
#include "scn/scn.h"
#include "vis_line.hh" #include "vis_line.hh"
template<typename LineType> template<typename LineType>
@ -270,17 +271,18 @@ grep_proc<LineType>::cleanup()
template<typename LineType> template<typename LineType>
void void
grep_proc<LineType>::dispatch_line(char* line) grep_proc<LineType>::dispatch_line(const string_fragment& line)
{ {
int start, end, capture_start; int start, end;
require(line != nullptr); require(line.is_valid());
if (sscanf(line, "h%d", this->gp_highest_line.out()) == 1) { auto sv = line.to_string_view();
} else if (sscanf(line, "%d", this->gp_last_line.out()) == 1) { if (scn::scan(sv, "h{}", this->gp_highest_line.lvalue())) {
} else if (scn::scan(sv, "{}", this->gp_last_line.lvalue())) {
/* Starting a new line with matches. */ /* Starting a new line with matches. */
ensure(this->gp_last_line >= 0); ensure(this->gp_last_line >= 0);
} else if (sscanf(line, "[%d:%d]", &start, &end) == 2) { } else if (scn::scan(sv, "[{}:{}]", start, end)) {
require(start >= 0); require(start >= 0);
require(end >= 0); require(end >= 0);
@ -288,25 +290,30 @@ grep_proc<LineType>::dispatch_line(char* line)
if (this->gp_sink != nullptr) { if (this->gp_sink != nullptr) {
this->gp_sink->grep_match(*this, this->gp_last_line, start, end); this->gp_sink->grep_match(*this, this->gp_last_line, start, end);
} }
} else if (sscanf(line, "(%d:%d)%n", &start, &end, &capture_start) == 2) {
require(start == -1 || start >= 0);
require(end >= 0);
/* Pass the captured strings to the sink delegate. */
if (this->gp_sink != nullptr) {
this->gp_sink->grep_capture(
*this,
this->gp_last_line,
start,
end,
start < 0 ? nullptr : &line[capture_start]);
}
} else if (line[0] == '/') { } else if (line[0] == '/') {
if (this->gp_sink != nullptr) { if (this->gp_sink != nullptr) {
this->gp_sink->grep_match_end(*this, this->gp_last_line); this->gp_sink->grep_match_end(*this, this->gp_last_line);
} }
} else { } else {
log_error("bad line from child -- %s", line); auto scan_res = scn::scan(sv, "({}:{})", start, end);
if (scan_res) {
require(start == -1 || start >= 0);
require(end >= 0);
/* Pass the captured strings to the sink delegate. */
if (this->gp_sink != nullptr) {
this->gp_sink->grep_capture(
*this,
this->gp_last_line,
start,
end,
start < 0
? string_fragment{}
: to_string_fragment(scan_res.range_as_string_view()));
}
} else {
log_error("bad line from child -- %s", line);
}
} }
} }
@ -369,13 +376,8 @@ grep_proc<LineType>::check_poll_set(const std::vector<struct pollfd>& pollfds)
this->gp_pipe_range = li.li_file_range; this->gp_pipe_range = li.li_file_range;
this->gp_line_buffer.read_range(li.li_file_range) this->gp_line_buffer.read_range(li.li_file_range)
.then([this](auto sbr) { .then([this](auto sbr) {
auto_mem<char> buf;
buf = (char*) malloc(sbr.length() + 1);
sbr.rtrim(is_line_ending); sbr.rtrim(is_line_ending);
memcpy(buf, sbr.get_data(), sbr.length()); this->dispatch_line(sbr.to_string_fragment());
buf[sbr.length()] = '\0';
this->dispatch_line(buf);
}); });
loop_count += 1; loop_count += 1;

@ -148,9 +148,11 @@ public:
LineType line, LineType line,
int start, int start,
int end, int end,
char* capture){}; const string_fragment& capture)
{
}
virtual void grep_match_end(grep_proc<LineType>& gp, LineType line){}; virtual void grep_match_end(grep_proc<LineType>& gp, LineType line) {}
}; };
/** /**
@ -255,7 +257,7 @@ protected:
/** /**
* Dispatch a line received from the child. * Dispatch a line received from the child.
*/ */
void dispatch_line(char* line); void dispatch_line(const string_fragment& line);
/** /**
* Free any resources used by the object and make sure the child has been * Free any resources used by the object and make sure the child has been

@ -59,6 +59,7 @@
#include "fmtlib/fmt/format.h" #include "fmtlib/fmt/format.h"
#include "hasher.hh" #include "hasher.hh"
#include "line_buffer.hh" #include "line_buffer.hh"
#include "piper.looper.hh"
#include "scn/scn.h" #include "scn/scn.h"
using namespace std::chrono_literals; using namespace std::chrono_literals;
@ -136,7 +137,7 @@ private:
#define SYNCPOINT_SIZE (1024 * 1024) #define SYNCPOINT_SIZE (1024 * 1024)
line_buffer::gz_indexed::gz_indexed() line_buffer::gz_indexed::gz_indexed()
{ {
if ((this->inbuf = (Bytef*) malloc(Z_BUFSIZE)) == NULL) { if ((this->inbuf = auto_mem<Bytef>::malloc(Z_BUFSIZE)) == NULL) {
throw std::bad_alloc(); throw std::bad_alloc();
} }
} }
@ -192,7 +193,7 @@ line_buffer::gz_indexed::continue_stream()
} }
void void
line_buffer::gz_indexed::open(int fd, header_data& hd) line_buffer::gz_indexed::open(int fd, lnav::gzip::header& hd)
{ {
this->close(); this->close();
this->init_stream(); this->init_stream();
@ -239,9 +240,9 @@ line_buffer::gz_indexed::open(int fd, header_data& hd)
log_debug("%d: no gzip header data", fd); log_debug("%d: no gzip header data", fd);
break; break;
case 1: case 1:
hd.hd_mtime.tv_sec = gz_hd.time; hd.h_mtime.tv_sec = gz_hd.time;
hd.hd_name = std::string((char*) name); hd.h_name = std::string((char*) name);
hd.hd_comment = std::string((char*) comment); hd.h_comment = std::string((char*) comment);
break; break;
default: default:
log_error("%d: failed to read gzip header data", fd); log_error("%d: failed to read gzip header data", fd);
@ -409,11 +410,32 @@ line_buffer::set_fd(auto_fd& fd)
char gz_id[2 + 1 + 1 + 4]; char gz_id[2 + 1 + 1 + 4];
if (pread(fd, gz_id, sizeof(gz_id), 0) == sizeof(gz_id)) { if (pread(fd, gz_id, sizeof(gz_id), 0) == sizeof(gz_id)) {
if (gz_id[0] == 'L' && gz_id[1] == 0 && gz_id[2] == 'N' auto piper_hdr_opt = lnav::piper::read_header(fd, gz_id);
&& gz_id[3] == 1 && gz_id[4] == 0)
{ if (piper_hdr_opt) {
static intern_string_t SRC = intern_string::lookup("piper");
auto meta_buf = std::move(piper_hdr_opt.value());
auto meta_sf = string_fragment::from_bytes(meta_buf.in(),
meta_buf.size());
auto meta_parse_res
= lnav::piper::header_handlers.parser_for(SRC).of(
meta_sf);
if (meta_parse_res.isErr()) {
log_error("failed to parse piper header: %s",
meta_parse_res.unwrapErr()[0]
.to_attr_line()
.get_string()
.c_str());
throw error(EINVAL);
}
this->lb_line_metadata = true; this->lb_line_metadata = true;
this->lb_file_offset = 8; this->lb_file_offset
= lnav::piper::HEADER_SIZE + meta_buf.size();
this->lb_piper_header_size = this->lb_file_offset;
this->lb_header = meta_parse_res.unwrap();
} else if (gz_id[0] == '\037' && gz_id[1] == '\213') { } else if (gz_id[0] == '\037' && gz_id[1] == '\213') {
int gzfd = dup(fd); int gzfd = dup(fd);
@ -422,14 +444,19 @@ line_buffer::set_fd(auto_fd& fd)
close(gzfd); close(gzfd);
throw error(errno); throw error(errno);
} }
this->lb_gz_file.writeAccess()->open(gzfd, this->lb_header); lnav::gzip::header hdr;
this->lb_gz_file.writeAccess()->open(gzfd, hdr);
this->lb_compressed = true; this->lb_compressed = true;
this->lb_file_time = this->lb_header.hd_mtime.tv_sec; this->lb_file_time = hdr.h_mtime.tv_sec;
if (this->lb_file_time < 0) { if (this->lb_file_time < 0) {
this->lb_file_time = 0; this->lb_file_time = 0;
} }
this->lb_compressed_offset this->lb_compressed_offset
= lseek(this->lb_fd, 0, SEEK_CUR); = lseek(this->lb_fd, 0, SEEK_CUR);
if (!hdr.empty()) {
this->lb_header = std::move(hdr);
}
this->resize_buffer(INITIAL_COMPRESSED_BUFFER_SIZE); this->resize_buffer(INITIAL_COMPRESSED_BUFFER_SIZE);
} }
#ifdef HAVE_BZLIB_H #ifdef HAVE_BZLIB_H
@ -1035,7 +1062,7 @@ line_buffer::load_next_line(file_range prev_line)
require(this->lb_fd != -1); require(this->lb_fd != -1);
if (this->lb_line_metadata && prev_line.fr_offset == 0) { if (this->lb_line_metadata && prev_line.fr_offset == 0) {
prev_line.fr_offset = 8; prev_line.fr_offset = this->lb_piper_header_size;
} }
auto offset = prev_line.next_offset(); auto offset = prev_line.next_offset();

@ -46,8 +46,11 @@
#include "base/auto_mem.hh" #include "base/auto_mem.hh"
#include "base/file_range.hh" #include "base/file_range.hh"
#include "base/is_utf8.hh" #include "base/is_utf8.hh"
#include "base/lnav.gzip.hh"
#include "base/piper.file.hh"
#include "base/result.h" #include "base/result.h"
#include "log_level.hh" #include "log_level.hh"
#include "mapbox/variant.hpp"
#include "safe/safe.h" #include "safe/safe.h"
#include "shared_buffer.hh" #include "shared_buffer.hh"
@ -81,19 +84,6 @@ public:
int e_err; int e_err;
}; };
struct header_data {
timeval hd_mtime{};
auto_buffer hd_extra{auto_buffer::alloc(0)};
std::string hd_name;
std::string hd_comment;
bool empty() const
{
return this->hd_mtime.tv_sec == 0 && this->hd_extra.empty()
&& this->hd_name.empty() && this->hd_comment.empty();
}
};
#define GZ_WINSIZE 32768U /*> gzip's max supported dictionary is 15-bits */ #define GZ_WINSIZE 32768U /*> gzip's max supported dictionary is 15-bits */
#define GZ_RAW_MODE (-15) /*> Raw inflate data mode */ #define GZ_RAW_MODE (-15) /*> Raw inflate data mode */
#define GZ_HEADER_MODE (15 + 32) /*> Automatic zstd or gzip decoding */ #define GZ_HEADER_MODE (15 + 32) /*> Automatic zstd or gzip decoding */
@ -121,7 +111,7 @@ public:
void close(); void close();
void init_stream(); void init_stream();
void continue_stream(); void continue_stream();
void open(int fd, header_data& hd); void open(int fd, lnav::gzip::header& hd);
int stream_data(void* buf, size_t size); int stream_data(void* buf, size_t size);
void seek(off_t offset); void seek(off_t offset);
@ -263,7 +253,10 @@ public:
size_t get_buffer_size() const { return this->lb_buffer.size(); } size_t get_buffer_size() const { return this->lb_buffer.size(); }
const header_data& get_header_data() const { return this->lb_header; } using file_header_t
= mapbox::util::variant<lnav::gzip::header, lnav::piper::header>;
const file_header_t& get_header_data() const { return this->lb_header; }
void enable_cache(); void enable_cache();
@ -340,6 +333,7 @@ private:
safe_gz_indexed lb_gz_file; /*< File reader for gzipped files. */ safe_gz_indexed lb_gz_file; /*< File reader for gzipped files. */
bool lb_bz_file{false}; /*< Flag set for bzip2 compressed files. */ bool lb_bz_file{false}; /*< Flag set for bzip2 compressed files. */
bool lb_line_metadata{false}; bool lb_line_metadata{false};
size_t lb_piper_header_size{0};
auto_buffer lb_buffer{auto_buffer::alloc(DEFAULT_LINE_BUFFER_SIZE)}; auto_buffer lb_buffer{auto_buffer::alloc(DEFAULT_LINE_BUFFER_SIZE)};
nonstd::optional<auto_buffer> lb_alt_buffer; nonstd::optional<auto_buffer> lb_alt_buffer;
@ -374,7 +368,7 @@ private:
nonstd::optional<auto_fd> lb_cached_fd; nonstd::optional<auto_fd> lb_cached_fd;
header_data lb_header; file_header_t lb_header;
}; };
#endif #endif

@ -95,6 +95,7 @@
#include "CLI/CLI.hpp" #include "CLI/CLI.hpp"
#include "dump_internals.hh" #include "dump_internals.hh"
#include "environ_vtab.hh" #include "environ_vtab.hh"
#include "file_converter_manager.hh"
#include "filter_sub_source.hh" #include "filter_sub_source.hh"
#include "fstat_vtab.hh" #include "fstat_vtab.hh"
#include "grep_proc.hh" #include "grep_proc.hh"
@ -1914,6 +1915,8 @@ looper()
line_buffer::cleanup_cache(); line_buffer::cleanup_cache();
archive_manager::cleanup_cache(); archive_manager::cleanup_cache();
tailer::cleanup_cache(); tailer::cleanup_cache();
lnav::piper::cleanup();
file_converter_manager::cleanup();
ran_cleanup = true; ran_cleanup = true;
} }
} }
@ -2099,11 +2102,7 @@ print_user_msgs(std::vector<lnav::console::user_message> error_list,
return retval; return retval;
} }
enum class verbosity_t : int { verbosity_t verbosity = verbosity_t::standard;
quiet,
standard,
verbose,
};
int int
main(int argc, char* argv[]) main(int argc, char* argv[])
@ -2116,7 +2115,6 @@ main(int argc, char* argv[])
bool exec_stdin = false, load_stdin = false; bool exec_stdin = false, load_stdin = false;
mode_flags_t mode_flags; mode_flags_t mode_flags;
const char* LANG = getenv("LANG"); const char* LANG = getenv("LANG");
verbosity_t verbosity = verbosity_t::standard;
if (LANG == nullptr || strcmp(LANG, "C") == 0) { if (LANG == nullptr || strcmp(LANG, "C") == 0) {
setenv("LANG", "en_US.UTF-8", 1); setenv("LANG", "en_US.UTF-8", 1);
@ -2139,6 +2137,9 @@ main(int argc, char* argv[])
lnav_data.ld_flags |= LNF_SECURE_MODE; lnav_data.ld_flags |= LNF_SECURE_MODE;
} }
setenv("LNAV_HOME_DIR", lnav::paths::dotlnav().c_str(), 1);
setenv("LNAV_WORK_DIR", lnav::paths::workdir().c_str(), 1);
lnav_data.ld_exec_context.ec_sql_callback = sql_callback; lnav_data.ld_exec_context.ec_sql_callback = sql_callback;
lnav_data.ld_exec_context.ec_pipe_callback = pipe_callback; lnav_data.ld_exec_context.ec_pipe_callback = pipe_callback;
@ -2843,6 +2844,7 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
} }
for (auto& file_path : file_args) { for (auto& file_path : file_args) {
scrub_ansi_string(file_path, nullptr);
auto file_path_without_trailer = file_path; auto file_path_without_trailer = file_path;
auto file_loc = file_location_t{mapbox::util::no_init{}}; auto file_loc = file_location_t{mapbox::util::no_init{}};
auto_mem<char> abspath; auto_mem<char> abspath;
@ -2885,7 +2887,8 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
isc::to<curl_looper&, services::curl_streamer_t>().send( isc::to<curl_looper&, services::curl_streamer_t>().send(
[ul](auto& clooper) { clooper.add_request(ul); }); [ul](auto& clooper) { clooper.add_request(ul); });
} else if (file_path.find("://") != std::string::npos) { } else if (file_path.find("://") != std::string::npos) {
lnav_data.ld_commands.emplace_back( lnav_data.ld_commands.insert(
lnav_data.ld_commands.begin(),
fmt::format(FMT_STRING(":open {}"), file_path)); fmt::format(FMT_STRING(":open {}"), file_path));
} }
#endif #endif
@ -3037,7 +3040,8 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
retval = EXIT_FAILURE; retval = EXIT_FAILURE;
} }
nonstd::optional<ghc::filesystem::path> stdin_pattern; nonstd::optional<std::string> stdin_url;
ghc::filesystem::path stdin_dir;
if (load_stdin && !isatty(STDIN_FILENO) && !is_dev_null(STDIN_FILENO) if (load_stdin && !isatty(STDIN_FILENO) && !is_dev_null(STDIN_FILENO)
&& !exec_stdin) && !exec_stdin)
{ {
@ -3063,7 +3067,8 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
STDIN_NAME, auto_fd::dup_of(STDIN_FILENO), auto_fd{}); STDIN_NAME, auto_fd::dup_of(STDIN_FILENO), auto_fd{});
if (stdin_piper_res.isOk()) { if (stdin_piper_res.isOk()) {
auto stdin_piper = stdin_piper_res.unwrap(); auto stdin_piper = stdin_piper_res.unwrap();
stdin_pattern = stdin_piper.get_out_pattern(); stdin_url = stdin_piper.get_url();
stdin_dir = stdin_piper.get_out_dir();
lnav_data.ld_active_files lnav_data.ld_active_files
.fc_file_names[stdin_piper.get_name()] .fc_file_names[stdin_piper.get_name()]
.with_piper(stdin_piper) .with_piper(stdin_piper)
@ -3216,6 +3221,8 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
archive_manager::cleanup_cache(); archive_manager::cleanup_cache();
tailer::cleanup_cache(); tailer::cleanup_cache();
line_buffer::cleanup_cache(); line_buffer::cleanup_cache();
lnav::piper::cleanup();
file_converter_manager::cleanup();
wait_for_pipers(); wait_for_pipers();
rescan_files(true); rescan_files(true);
isc::to<curl_looper&, services::curl_streamer_t>() isc::to<curl_looper&, services::curl_streamer_t>()
@ -3352,10 +3359,10 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
// When reading from stdin, tell the user where the capture file is // When reading from stdin, tell the user where the capture file is
// stored so they can look at it later. // stored so they can look at it later.
if (stdin_pattern && !(lnav_data.ld_flags & LNF_HEADLESS)) { if (stdin_url && !(lnav_data.ld_flags & LNF_HEADLESS)) {
file_size_t stdin_size = 0; file_size_t stdin_size = 0;
for (const auto& ent : ghc::filesystem::directory_iterator( for (const auto& ent :
stdin_pattern.value().parent_path())) ghc::filesystem::directory_iterator(stdin_dir))
{ {
stdin_size += ent.file_size(); stdin_size += ent.file_size();
} }
@ -3371,7 +3378,7 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
"reopen it by running:\n") "reopen it by running:\n")
.appendf(FMT_STRING(" {} "), .appendf(FMT_STRING(" {} "),
lnav_data.ld_program_name) lnav_data.ld_program_name)
.append(lnav::roles::file(stdin_pattern.value())))); .append(lnav::roles::file(stdin_url.value()))));
} }
} }

@ -268,7 +268,14 @@ class main_looper
public: public:
}; };
enum class verbosity_t : int {
quiet,
standard,
verbose,
};
extern struct lnav_data_t lnav_data; extern struct lnav_data_t lnav_data;
extern verbosity_t verbosity;
extern readline_context::command_map_t lnav_commands; extern readline_context::command_map_t lnav_commands;
extern const int ZOOM_LEVELS[]; extern const int ZOOM_LEVELS[];

@ -167,8 +167,11 @@ public:
void scanned_file(const std::shared_ptr<logfile>& lf) override void scanned_file(const std::shared_ptr<logfile>& lf) override
{ {
if (!lnav_data.ld_files_to_front.empty() const auto& ftf = lnav_data.ld_files_to_front;
&& lnav_data.ld_files_to_front.front().first == lf->get_filename())
if (!ftf.empty()
&& (ftf.front().first == lf->get_filename()
|| ftf.front().first == lf->get_open_options().loo_filename))
{ {
this->front_file = lf; this->front_file = lf;
this->front_top = lnav_data.ld_files_to_front.front().second; this->front_top = lnav_data.ld_files_to_front.front().second;

@ -29,9 +29,14 @@
#include "lnav.management_cli.hh" #include "lnav.management_cli.hh"
#include "base/fs_util.hh"
#include "base/humanize.hh"
#include "base/humanize.time.hh"
#include "base/itertools.hh" #include "base/itertools.hh"
#include "base/paths.hh"
#include "base/result.h" #include "base/result.h"
#include "base/string_util.hh" #include "base/string_util.hh"
#include "fmt/chrono.h"
#include "fmt/format.h" #include "fmt/format.h"
#include "itertools.similar.hh" #include "itertools.similar.hh"
#include "lnav.hh" #include "lnav.hh"
@ -39,6 +44,7 @@
#include "log_format.hh" #include "log_format.hh"
#include "log_format_ext.hh" #include "log_format_ext.hh"
#include "mapbox/variant.hpp" #include "mapbox/variant.hpp"
#include "piper.looper.hh"
#include "regex101.import.hh" #include "regex101.import.hh"
#include "session_data.hh" #include "session_data.hh"
@ -61,7 +67,9 @@ symbol_reducer(const std::string& elem, attr_line_t& accum)
inline attr_line_t& inline attr_line_t&
subcmd_reducer(const CLI::App* app, attr_line_t& accum) subcmd_reducer(const CLI::App* app, attr_line_t& accum)
{ {
return accum.append("\n \u2022 ") return accum.append("\n ")
.append("\u2022"_list_glyph)
.append(" ")
.append(lnav::roles::keyword(app->get_name())) .append(lnav::roles::keyword(app->get_name()))
.append(": ") .append(": ")
.append(app->get_description()); .append(app->get_description());
@ -673,6 +681,283 @@ struct subcmd_format_t {
} }
}; };
struct subcmd_piper_t {
using action_t = std::function<perform_result_t(const subcmd_piper_t&)>;
CLI::App* sp_app{nullptr};
action_t sp_action;
subcmd_piper_t& set_action(action_t act)
{
if (!this->sp_action) {
this->sp_action = std::move(act);
}
return *this;
}
static perform_result_t default_action(const subcmd_piper_t& sp)
{
auto um = console::user_message::error(
"expecting an operation related to piper storage");
um.with_help(
sp.sp_app->get_subcommands({})
| lnav::itertools::fold(
subcmd_reducer, attr_line_t{"the available operations are:"}));
return {um};
}
static perform_result_t list_action(const subcmd_piper_t&)
{
static const intern_string_t SRC = intern_string::lookup("piper");
static const auto DOT_HEADER = ghc::filesystem::path(".header");
struct item {
lnav::piper::header i_header;
std::string i_url;
file_size_t i_total_size{0};
};
file_size_t grand_total{0};
std::vector<item> items;
std::error_code ec;
for (const auto& instance_dir : ghc::filesystem::directory_iterator(
lnav::piper::storage_path(), ec))
{
if (!instance_dir.is_directory()) {
log_warning("piper directory entry is not a directory: %s",
instance_dir.path().c_str());
continue;
}
nonstd::optional<lnav::piper::header> hdr_opt;
auto url = fmt::format(FMT_STRING("piper://{}"),
instance_dir.path().filename().string());
file_size_t total_size{0};
auto hdr_path = instance_dir / DOT_HEADER;
if (ghc::filesystem::exists(hdr_path)) {
auto hdr_read_res = lnav::filesystem::read_file(hdr_path);
if (hdr_read_res.isOk()) {
auto parse_res
= lnav::piper::header_handlers.parser_for(SRC).of(
hdr_read_res.unwrap());
if (parse_res.isOk()) {
hdr_opt = parse_res.unwrap();
} else {
log_error("failed to parse header: %s -- %s",
hdr_path.c_str(),
parse_res.unwrapErr()[0]
.to_attr_line()
.get_string()
.c_str());
}
} else {
log_error("failed to read header file: %s -- %s",
hdr_path.c_str(),
hdr_read_res.unwrapErr().c_str());
}
}
for (const auto& entry :
ghc::filesystem::directory_iterator(instance_dir.path()))
{
if (entry.path().filename() == DOT_HEADER) {
continue;
}
total_size += entry.file_size();
char buffer[lnav::piper::HEADER_SIZE];
auto entry_open_res
= lnav::filesystem::open_file(entry.path(), O_RDONLY);
if (entry_open_res.isErr()) {
log_warning("unable to open piper file: %s -- %s",
entry.path().c_str(),
entry_open_res.unwrapErr().c_str());
continue;
}
auto entry_fd = entry_open_res.unwrap();
if (read(entry_fd, buffer, sizeof(buffer)) != sizeof(buffer)) {
log_warning("piper file is too small: %s",
entry.path().c_str());
continue;
}
auto hdr_bits_opt = lnav::piper::read_header(entry_fd, buffer);
if (!hdr_bits_opt) {
log_warning("could not read piper header: %s",
entry.path().c_str());
continue;
}
auto hdr_buf = std::move(hdr_bits_opt.value());
total_size -= hdr_buf.size();
auto hdr_sf
= string_fragment::from_bytes(hdr_buf.in(), hdr_buf.size());
auto hdr_parse_res
= lnav::piper::header_handlers.parser_for(SRC).of(hdr_sf);
if (hdr_parse_res.isErr()) {
log_error("failed to parse piper header: %s",
hdr_parse_res.unwrapErr()[0]
.to_attr_line()
.get_string()
.c_str());
continue;
}
auto hdr = hdr_parse_res.unwrap();
if (!hdr_opt || hdr < hdr_opt.value()) {
hdr_opt = hdr;
}
}
if (hdr_opt) {
items.emplace_back(item{hdr_opt.value(), url, total_size});
}
grand_total += total_size;
}
if (ec && ec.value() != ENOENT) {
auto um = lnav::console::user_message::error(
attr_line_t("unable to access piper directory: ")
.append(lnav::roles::file(
lnav::piper::storage_path().string())))
.with_reason(ec.message());
return {um};
}
if (items.empty()) {
if (verbosity != verbosity_t::quiet) {
auto um
= lnav::console::user_message::info(
attr_line_t("no piper captures were found in:\n\t")
.append(lnav::roles::file(
lnav::piper::storage_path().string())))
.with_help(
attr_line_t("You can create a capture by "
"piping data into ")
.append(lnav::roles::file("lnav"))
.append(" or using the ")
.append_quoted(lnav::roles::symbol(":sh"))
.append(" command"));
return {um};
}
return {};
}
auto txt
= items
| lnav::itertools::sort_with([](const item& lhs, const item& rhs) {
if (lhs.i_header < rhs.i_header) {
return true;
}
if (rhs.i_header < lhs.i_header) {
return false;
}
return lhs.i_url < rhs.i_url;
})
| lnav::itertools::map([](const item& it) {
auto ago = humanize::time::point::from_tv(it.i_header.h_ctime)
.as_time_ago();
auto retval = attr_line_t()
.append(lnav::roles::list_glyph(
fmt::format(FMT_STRING("{:>18}"), ago)))
.append(" ")
.append(lnav::roles::file(it.i_url))
.append(" ")
.append(lnav::roles::number(fmt::format(
FMT_STRING("{:>8}"),
humanize::file_size(
it.i_total_size,
humanize::alignment::columnar))))
.append(" ")
.append_quoted(lnav::roles::comment(
it.i_header.h_name))
.append("\n");
if (verbosity == verbosity_t::verbose) {
auto env_al
= it.i_header.h_env
| lnav::itertools::map([](const auto& pair) {
return attr_line_t()
.append(lnav::roles::identifier(pair.first))
.append("=")
.append(pair.second)
.append("\n");
})
| lnav::itertools::fold(
[](const auto& elem, auto& accum) {
if (!accum.empty()) {
accum.append(28, ' ');
}
return accum.append(elem);
},
attr_line_t());
retval.append(23, ' ')
.append("cwd: ")
.append(lnav::roles::file(it.i_header.h_cwd))
.append("\n")
.append(23, ' ')
.append("env: ")
.append(env_al);
}
return retval;
})
| lnav::itertools::fold(
[](const auto& elem, auto& accum) {
return accum.append(elem);
},
attr_line_t{});
txt.rtrim();
perform_result_t retval;
if (verbosity != verbosity_t::quiet) {
auto extra_um
= lnav::console::user_message::info(
attr_line_t(
"the following piper captures were found in:\n\t")
.append(lnav::roles::file(
lnav::piper::storage_path().string())))
.with_note(
attr_line_t("The captures currently consume ")
.append(lnav::roles::number(humanize::file_size(
grand_total, humanize::alignment::none)))
.append(" of disk space. File sizes include "
"associated metadata."))
.with_help(
"You can reopen a capture by passing the piper URL "
"to lnav");
retval.emplace_back(extra_um);
}
retval.emplace_back(lnav::console::user_message::raw(txt));
return retval;
}
static perform_result_t clean_action(const subcmd_piper_t&)
{
std::error_code ec;
ghc::filesystem::remove_all(lnav::piper::storage_path(), ec);
if (ec) {
return {
lnav::console::user_message::error(
"unable to remove piper storage directory")
.with_reason(ec.message()),
};
}
return {};
}
};
struct subcmd_regex101_t { struct subcmd_regex101_t {
using action_t = std::function<perform_result_t(const subcmd_regex101_t&)>; using action_t = std::function<perform_result_t(const subcmd_regex101_t&)>;
@ -762,8 +1047,11 @@ struct subcmd_regex101_t {
} }
}; };
using operations_v = mapbox::util:: using operations_v = mapbox::util::variant<no_subcmd_t,
variant<no_subcmd_t, subcmd_config_t, subcmd_format_t, subcmd_regex101_t>; subcmd_config_t,
subcmd_format_t,
subcmd_piper_t,
subcmd_regex101_t>;
class operations { class operations {
public: public:
@ -783,6 +1071,7 @@ describe_cli(CLI::App& app, int argc, char* argv[])
subcmd_config_t config_args; subcmd_config_t config_args;
subcmd_format_t format_args; subcmd_format_t format_args;
subcmd_piper_t piper_args;
subcmd_regex101_t regex101_args; subcmd_regex101_t regex101_args;
{ {
@ -910,6 +1199,25 @@ describe_cli(CLI::App& app, int argc, char* argv[])
} }
} }
{
auto* subcmd_piper
= app.add_subcommand("piper", "perform operations on piper storage")
->callback([&]() {
piper_args.set_action(subcmd_piper_t::default_action);
retval->o_ops = piper_args;
});
piper_args.sp_app = subcmd_piper;
subcmd_piper
->add_subcommand("list", "print the available piper captures")
->callback(
[&]() { piper_args.set_action(subcmd_piper_t::list_action); });
subcmd_piper->add_subcommand("clean", "remove all piper captures")
->callback(
[&]() { piper_args.set_action(subcmd_piper_t::clean_action); });
}
{ {
auto* subcmd_regex101 auto* subcmd_regex101
= app.add_subcommand("regex101", = app.add_subcommand("regex101",
@ -978,6 +1286,7 @@ perform(std::shared_ptr<operations> opts)
}, },
[](const subcmd_config_t& sc) { return sc.sc_action(sc); }, [](const subcmd_config_t& sc) { return sc.sc_action(sc); },
[](const subcmd_format_t& sf) { return sf.sf_action(sf); }, [](const subcmd_format_t& sf) { return sf.sf_action(sf); },
[](const subcmd_piper_t& sp) { return sp.sp_action(sp); },
[](const subcmd_regex101_t& sr) { return sr.sr_action(sr); }); [](const subcmd_regex101_t& sr) { return sr.sr_action(sr); });
} }

@ -2602,6 +2602,8 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
retval = "info: watching -- " + fn; retval = "info: watching -- " + fn;
} else if (is_glob(fn.c_str())) { } else if (is_glob(fn.c_str())) {
fc.fc_file_names.emplace(fn, loo); fc.fc_file_names.emplace(fn, loo);
files_to_front.emplace_back(
loo.loo_filename.empty() ? fn : loo.loo_filename, file_loc);
retval = "info: watching -- " + fn; retval = "info: watching -- " + fn;
} else if (stat(fn.c_str(), &st) == -1) { } else if (stat(fn.c_str(), &st) == -1) {
if (fn.find(':') != std::string::npos) { if (fn.find(':') != std::string::npos) {
@ -4204,6 +4206,8 @@ com_sh(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
return ec.make_error("{} -- unavailable in secure mode", args[0]); return ec.make_error("{} -- unavailable in secure mode", args[0]);
} }
static size_t EXEC_COUNT = 0;
if (!ec.ec_dry_run) { if (!ec.ec_dry_run) {
auto carg = trim(cmdline.substr(args[0].size())); auto carg = trim(cmdline.substr(args[0].size()));
@ -4261,7 +4265,8 @@ com_sh(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
} }
auto display_name = ec.get_provenance<exec_context::file_open>() auto display_name = ec.get_provenance<exec_context::file_open>()
.value_or(exec_context::file_open{carg}) .value_or(exec_context::file_open{fmt::format(
FMT_STRING("[{}] {}"), EXEC_COUNT++, carg)})
.fo_name; .fo_name;
auto create_piper_res auto create_piper_res
= lnav::piper::create_looper(display_name, = lnav::piper::create_looper(display_name,

@ -1074,6 +1074,14 @@ static const struct json_path_container piper_handlers = {
.with_min_value(2) .with_min_value(2)
.with_description("The number of rotated files to keep") .with_description("The number of rotated files to keep")
.for_field(&_lnav_config::lc_piper, &lnav::piper::config::c_rotations), .for_field(&_lnav_config::lc_piper, &lnav::piper::config::c_rotations),
yajlpp::property_handler("ttl")
.with_synopsis("<duration>")
.with_description(
"The time-to-live for captured data, expressed as a duration "
"(e.g. '3d' for three days)")
.with_example("3d")
.with_example("12h")
.for_field(&_lnav_config::lc_piper, &lnav::piper::config::c_ttl),
}; };
static const struct json_path_container file_vtab_handlers = { static const struct json_path_container file_vtab_handlers = {

@ -51,20 +51,20 @@
#include "log.watch.hh" #include "log.watch.hh"
#include "log_format.hh" #include "log_format.hh"
#include "logfile.cfg.hh" #include "logfile.cfg.hh"
#include "piper.looper.hh"
#include "yajlpp/yajlpp_def.hh" #include "yajlpp/yajlpp_def.hh"
static auto intern_lifetime = intern_string::get_table_lifetime(); static auto intern_lifetime = intern_string::get_table_lifetime();
static const size_t INDEX_RESERVE_INCREMENT = 1024; static const size_t INDEX_RESERVE_INCREMENT = 1024;
static const typed_json_path_container<line_buffer::header_data> static const typed_json_path_container<lnav::gzip::header> file_header_handlers
file_header_handlers = { = {
yajlpp::property_handler("name").for_field( yajlpp::property_handler("name").for_field(&lnav::gzip::header::h_name),
&line_buffer::header_data::hd_name),
yajlpp::property_handler("mtime").for_field( yajlpp::property_handler("mtime").for_field(
&line_buffer::header_data::hd_mtime), &lnav::gzip::header::h_mtime),
yajlpp::property_handler("comment").for_field( yajlpp::property_handler("comment").for_field(
&line_buffer::header_data::hd_comment), &lnav::gzip::header::h_comment),
}; };
Result<std::shared_ptr<logfile>, std::string> Result<std::shared_ptr<logfile>, std::string>
@ -134,10 +134,25 @@ logfile::open(std::string filename, const logfile_open_options& loo, auto_fd fd)
lf->lf_indexing = lf->lf_options.loo_is_visible; lf->lf_indexing = lf->lf_options.loo_is_visible;
const auto& hdr = lf->lf_line_buffer.get_header_data(); const auto& hdr = lf->lf_line_buffer.get_header_data();
if (!hdr.empty()) { log_info("%s: has header %d", lf->lf_filename.c_str(), hdr.valid());
lf->lf_embedded_metadata["net.zlib.gzip.header"] hdr.match(
= {text_format_t::TF_JSON, file_header_handlers.to_string(hdr)}; [&lf](const lnav::gzip::header& gzhdr) {
} if (!gzhdr.empty()) {
lf->lf_embedded_metadata["net.zlib.gzip.header"] = {
text_format_t::TF_JSON,
file_header_handlers.to_string(gzhdr),
};
}
},
[&lf](const lnav::piper::header& phdr) {
lf->lf_embedded_metadata["org.lnav.piper.header"] = {
text_format_t::TF_JSON,
lnav::piper::header_handlers.to_string(phdr),
};
log_debug("setting file name: %s", phdr.h_name.c_str());
lf->set_filename(phdr.h_name);
lf->lf_valid_filename = false;
});
ensure(lf->invariant()); ensure(lf->invariant());

@ -32,6 +32,7 @@
#include "piper.looper.hh" #include "piper.looper.hh"
#include <arpa/inet.h>
#include <poll.h> #include <poll.h>
#include "base/fs_util.hh" #include "base/fs_util.hh"
@ -41,6 +42,7 @@
#include "config.h" #include "config.h"
#include "hasher.hh" #include "hasher.hh"
#include "line_buffer.hh" #include "line_buffer.hh"
#include "pcrepp/pcre2pp.hh"
#include "piper.looper.cfg.hh" #include "piper.looper.cfg.hh"
using namespace std::chrono_literals; using namespace std::chrono_literals;
@ -62,19 +64,60 @@ write_timestamp(int fd, log_level_t level, off_t woff)
return pwrite(fd, time_str, fmt_res.size, woff); return pwrite(fd, time_str, fmt_res.size, woff);
} }
extern char** environ;
namespace lnav { namespace lnav {
namespace piper { namespace piper {
const json_path_container header_env_handlers = {
yajlpp::pattern_property_handler("(?<name>.*)")
.with_synopsis("<name>")
.for_field(&lnav::piper::header::h_env),
};
const typed_json_path_container<lnav::piper::header> header_handlers = {
yajlpp::property_handler("name").for_field(&lnav::piper::header::h_name),
yajlpp::property_handler("ctime").for_field(&lnav::piper::header::h_ctime),
yajlpp::property_handler("cwd").for_field(&lnav::piper::header::h_cwd),
yajlpp::property_handler("env").with_children(header_env_handlers),
};
static std::map<std::string, std::string>
environ_to_map()
{
static const auto SENSITIVE_VARS
= lnav::pcre2pp::code::from_const(R"((?i)token|pass)");
std::map<std::string, std::string> retval;
for (size_t lpc = 0; environ[lpc]; lpc++) {
auto full_sf = string_fragment::from_c_str(environ[lpc]);
auto pair_opt = full_sf.split_pair(string_fragment::tag1{'='});
if (!pair_opt) {
continue;
}
if (SENSITIVE_VARS.find_in(pair_opt->first).ignore_error()) {
retval[pair_opt->first.to_string()] = "******";
} else {
retval[pair_opt->first.to_string()] = pair_opt->second.to_string();
}
}
return retval;
}
looper::looper(std::string name, auto_fd stdout_fd, auto_fd stderr_fd) looper::looper(std::string name, auto_fd stdout_fd, auto_fd stderr_fd)
: l_name(std::move(name)), l_stdout(std::move(stdout_fd)), : l_name(std::move(name)), l_cwd(ghc::filesystem::current_path().string()),
l_env(environ_to_map()), l_stdout(std::move(stdout_fd)),
l_stderr(std::move(stderr_fd)) l_stderr(std::move(stderr_fd))
{ {
size_t count = 0; size_t count = 0;
do { do {
this->l_out_dir this->l_out_dir
= lnav::paths::workdir() = storage_path()
/ fmt::format( / fmt::format(
FMT_STRING("piper-{}-{}"), FMT_STRING("p-{}-{:03}"),
hasher().update(getmstime()).update(l_name).to_string(), hasher().update(getmstime()).update(l_name).to_string(),
count); count);
count += 1; count += 1;
@ -237,6 +280,22 @@ looper::loop()
break; break;
} }
auto hdr_path = this->l_out_dir / ".header";
auto hdr = header{
current_timeval(),
this->l_name,
this->l_cwd,
this->l_env,
};
auto write_hdr_res = lnav::filesystem::write_file(
hdr_path, header_handlers.to_string(hdr));
if (write_hdr_res.isErr()) {
log_error("unable to write header file: %s -- %s",
hdr_path.c_str(),
write_hdr_res.unwrapErr().c_str());
break;
}
outfd = create_res.unwrap(); outfd = create_res.unwrap();
auto header_avail = cap.lb.get_available(); auto header_avail = cap.lb.get_available();
auto read_res = cap.lb.read_range(header_avail); auto read_res = cap.lb.read_range(header_avail);
@ -299,11 +358,41 @@ looper::loop()
outfd = create_res.unwrap(); outfd = create_res.unwrap();
rotate_count += 1; rotate_count += 1;
static const char lnav_header[] auto hdr = header{
= {'L', 0, 'N', 1, 0, 0, 0, 0}; current_timeval(),
auto prc this->l_name,
= write(outfd.get(), lnav_header, sizeof(lnav_header)); this->l_cwd,
woff = prc; this->l_env,
};
woff = 0;
auto hdr_str = header_handlers.to_string(hdr);
uint32_t meta_size = htonl(hdr_str.length());
auto prc = write(
outfd.get(), HEADER_MAGIC, sizeof(HEADER_MAGIC));
if (prc < sizeof(HEADER_MAGIC)) {
log_error("unable to write file header: %s -- %s",
this->l_name.c_str(),
strerror(errno));
break;
}
woff += prc;
prc = write(outfd.get(), &meta_size, sizeof(meta_size));
if (prc < sizeof(meta_size)) {
log_error("unable to write file header: %s -- %s",
this->l_name.c_str(),
strerror(errno));
break;
}
woff += prc;
prc = write(outfd.get(), hdr_str.c_str(), hdr_str.size());
if (prc < hdr_str.size()) {
log_error("unable to write file header: %s -- %s",
this->l_name.c_str(),
strerror(errno));
break;
}
woff += prc;
} }
ssize_t wrc; ssize_t wrc;
@ -352,5 +441,47 @@ create_looper(std::string name, auto_fd stdout_fd, auto_fd stderr_fd)
name, std::move(stdout_fd), std::move(stderr_fd)))); name, std::move(stdout_fd), std::move(stderr_fd))));
} }
void
cleanup()
{
(void) std::async(std::launch::async, []() {
const auto& cfg = injector::get<const config&>();
auto now = std::chrono::system_clock::now();
auto cache_path = storage_path();
std::vector<ghc::filesystem::path> to_remove;
for (const auto& cache_subdir :
ghc::filesystem::directory_iterator(cache_path))
{
auto mtime = ghc::filesystem::last_write_time(cache_subdir.path());
auto exp_time = mtime + cfg.c_ttl;
if (now < exp_time) {
continue;
}
bool is_recent = false;
for (const auto& entry :
ghc::filesystem::directory_iterator(cache_subdir))
{
auto mtime = ghc::filesystem::last_write_time(entry.path());
auto exp_time = mtime + cfg.c_ttl;
if (now < exp_time) {
is_recent = true;
break;
}
}
if (!is_recent) {
to_remove.emplace_back(cache_subdir);
}
}
for (auto& entry : to_remove) {
log_debug("removing piper directory: %s", entry.c_str());
ghc::filesystem::remove_all(entry);
}
});
}
} // namespace piper } // namespace piper
} // namespace lnav } // namespace lnav

@ -38,6 +38,7 @@ namespace piper {
struct config { struct config {
uint64_t c_max_size{10ULL * 1024ULL * 1024ULL}; uint64_t c_max_size{10ULL * 1024ULL * 1024ULL};
uint32_t c_rotations{4}; uint32_t c_rotations{4};
std::chrono::seconds c_ttl{std::chrono::hours(48)};
}; };
} // namespace piper } // namespace piper

@ -35,8 +35,10 @@
#include <string> #include <string>
#include "base/auto_fd.hh" #include "base/auto_fd.hh"
#include "base/piper.file.hh"
#include "base/result.h" #include "base/result.h"
#include "ghc/filesystem.hpp" #include "ghc/filesystem.hpp"
#include "yajlpp/yajlpp_def.hh"
namespace lnav { namespace lnav {
namespace piper { namespace piper {
@ -61,6 +63,12 @@ public:
return this->l_out_dir / "out.*"; return this->l_out_dir / "out.*";
} }
std::string get_url() const
{
return fmt::format(FMT_STRING("piper://{}"),
this->l_out_dir.filename().string());
}
bool is_finished() const bool is_finished() const
{ {
return this->l_future.wait_for(std::chrono::seconds(0)) return this->l_future.wait_for(std::chrono::seconds(0))
@ -72,6 +80,8 @@ private:
std::atomic<bool> l_looping{true}; std::atomic<bool> l_looping{true};
const std::string l_name; const std::string l_name;
const std::string l_cwd;
const std::map<std::string, std::string> l_env;
ghc::filesystem::path l_out_dir; ghc::filesystem::path l_out_dir;
auto_fd l_stdout; auto_fd l_stdout;
auto_fd l_stderr; auto_fd l_stderr;
@ -98,6 +108,8 @@ public:
return this->h_looper->get_out_pattern(); return this->h_looper->get_out_pattern();
} }
std::string get_url() const { return this->h_looper->get_url(); }
bool is_finished() const { return this->h_looper->is_finished(); } bool is_finished() const { return this->h_looper->is_finished(); }
bool operator==(const handle& other) const bool operator==(const handle& other) const
@ -109,12 +121,16 @@ private:
std::shared_ptr<looper> h_looper; std::shared_ptr<looper> h_looper;
}; };
extern const typed_json_path_container<lnav::piper::header> header_handlers;
using running_handle = handle<state::running>; using running_handle = handle<state::running>;
Result<handle<state::running>, std::string> create_looper(std::string name, Result<handle<state::running>, std::string> create_looper(std::string name,
auto_fd stdout_fd, auto_fd stdout_fd,
auto_fd stderr_fd); auto_fd stderr_fd);
void cleanup();
} // namespace piper } // namespace piper
} // namespace lnav } // namespace lnav

@ -188,7 +188,7 @@ pretty_printer::write_element(const pretty_printer::element& el)
} }
ssize_t start_size = this->pp_stream.tellp(); ssize_t start_size = this->pp_stream.tellp();
if (el.e_token == DT_QUOTED_STRING) { if (el.e_token == DT_QUOTED_STRING) {
auto_mem<char> unquoted_str((char*) malloc(el.e_capture.length() + 1)); auto unquoted_str = auto_mem<char>::malloc(el.e_capture.length() + 1);
const char* start const char* start
= this->pp_scanner->to_string_fragment(el.e_capture).data(); = this->pp_scanner->to_string_fragment(el.e_capture).data();
auto unq_len = unquote(unquoted_str.in(), start, el.e_capture.length()); auto unq_len = unquote(unquoted_str.in(), start, el.e_capture.length());

@ -28,7 +28,8 @@
}, },
"piper": { "piper": {
"max-size": 10485760, "max-size": 10485760,
"rotations": 4 "rotations": 4,
"ttl": "2d"
}, },
"clipboard": { "clipboard": {
"impls": { "impls": {
@ -82,6 +83,9 @@
"url-scheme": { "url-scheme": {
"docker": { "docker": {
"handler": "docker-url-handler" "handler": "docker-url-handler"
},
"piper": {
"handler": "piper-url-handler"
} }
} }
} }

@ -0,0 +1,8 @@
#
# @synopsis: piper-url-handler
# @description: Internal script to handle opening piper URLs
#
;SELECT jget(url, '/host') AS uhost FROM (SELECT parse_url($1) AS url)
:open ${LNAV_WORK_DIR}/piper/$uhost/out.*

@ -4,6 +4,7 @@ BUILTIN_LNAVSCRIPTS = \
$(srcdir)/scripts/docker-url-handler.lnav \ $(srcdir)/scripts/docker-url-handler.lnav \
$(srcdir)/scripts/lnav-pop-view.lnav \ $(srcdir)/scripts/lnav-pop-view.lnav \
$(srcdir)/scripts/partition-by-boot.lnav \ $(srcdir)/scripts/partition-by-boot.lnav \
$(srcdir)/scripts/piper-url-handler.lnav \
$(srcdir)/scripts/rename-stdin.lnav \ $(srcdir)/scripts/rename-stdin.lnav \
$(srcdir)/scripts/search-for.lnav \ $(srcdir)/scripts/search-for.lnav \
$() $()

@ -365,8 +365,8 @@ sparkline_final(sqlite3_context* context)
return; return;
} }
auto* retval = (char*) malloc(sc->sc_values.size() * 3 + 1); auto retval = auto_mem<char>::malloc(sc->sc_values.size() * 3 + 1);
auto* start = retval; auto* start = retval.in();
for (const auto& value : sc->sc_values) { for (const auto& value : sc->sc_values) {
auto bar = humanize::sparkline(value, sc->sc_max_value); auto bar = humanize::sparkline(value, sc->sc_max_value);
@ -376,7 +376,7 @@ sparkline_final(sqlite3_context* context)
} }
*start = '\0'; *start = '\0';
sqlite3_result_text(context, retval, -1, free); to_sqlite(context, std::move(retval));
sc->~sparkline_context(); sc->~sparkline_context();
} }
@ -720,13 +720,8 @@ sql_parse_url(std::string url)
while (true) { while (true) {
auto split_res auto split_res
= remaining.split_when(string_fragment::tag1{'&'}); = remaining.split_when(string_fragment::tag1{'&'});
if (!split_res) {
break;
}
auto_mem<char> kv_pair(curl_free); auto_mem<char> kv_pair(curl_free);
auto kv_pair_encoded = split_res->first; auto kv_pair_encoded = split_res.first;
int out_len = 0; int out_len = 0;
kv_pair = curl_easy_unescape(CURL_HANDLE, kv_pair = curl_easy_unescape(CURL_HANDLE,
@ -748,20 +743,20 @@ sql_parse_url(std::string url)
query_map.gen(val); query_map.gen(val);
} }
} else { } else {
auto val_str = split_res->first.to_string(); auto val_str = split_res.first.to_string();
if (seen_keys.count(val_str) == 0) { if (seen_keys.count(val_str) == 0) {
seen_keys.insert(val_str); seen_keys.insert(val_str);
query_map.gen(split_res->first); query_map.gen(split_res.first);
query_map.gen(); query_map.gen();
} }
} }
if (split_res->second.empty()) { if (split_res.second.empty()) {
break; break;
} }
remaining = split_res->second; remaining = split_res.second;
} }
} else { } else {
root.gen("query"); root.gen("query");

@ -45,59 +45,55 @@ template<typename T, class DISTINCT>
class strong_int { class strong_int {
public: public:
explicit constexpr strong_int(T v = 0) noexcept : value(v){}; explicit constexpr strong_int(T v = 0) noexcept : value(v){};
operator const T&() const operator const T&() const { return this->value; }
{
return this->value;
};
strong_int operator+(const strong_int& rhs) const strong_int operator+(const strong_int& rhs) const
{ {
return strong_int(this->value + rhs.value); return strong_int(this->value + rhs.value);
}; }
strong_int operator-(const strong_int& rhs) const strong_int operator-(const strong_int& rhs) const
{ {
return strong_int(this->value - rhs.value); return strong_int(this->value - rhs.value);
}; }
strong_int operator/(const strong_int& rhs) const strong_int operator/(const strong_int& rhs) const
{ {
return strong_int(this->value / rhs.value); return strong_int(this->value / rhs.value);
}; }
bool operator<(const strong_int& rhs) const bool operator<(const strong_int& rhs) const
{ {
return this->value < rhs.value; return this->value < rhs.value;
}; }
strong_int& operator+=(const strong_int& rhs) strong_int& operator+=(const strong_int& rhs)
{ {
this->value += rhs.value; this->value += rhs.value;
return *this; return *this;
}; }
strong_int& operator-=(const strong_int& rhs) strong_int& operator-=(const strong_int& rhs)
{ {
this->value -= rhs.value; this->value -= rhs.value;
return *this; return *this;
}; }
strong_int& operator-() strong_int& operator-()
{ {
this->value = -this->value; this->value = -this->value;
return *this; return *this;
}; }
strong_int& operator++() strong_int& operator++()
{ {
this->value++; this->value++;
return *this; return *this;
}; }
strong_int& operator--() strong_int& operator--()
{ {
this->value--; this->value--;
return *this; return *this;
}; }
bool operator==(const strong_int& rhs) const bool operator==(const strong_int& rhs) const
{ {
return this->value == rhs.value; return this->value == rhs.value;
}; }
T* out() T* out() { return &this->value; }
{
return &this->value; T& lvalue() { return this->value; }
};
private: private:
T value; T value;

@ -787,9 +787,7 @@ tailer::looper::host_tailer::loop_body()
} }
constexpr int64_t BUFFER_SIZE = 4 * 1024 * 1024; constexpr int64_t BUFFER_SIZE = 4 * 1024 * 1024;
auto_mem<unsigned char> buffer; auto buffer = auto_mem<unsigned char>::malloc(BUFFER_SIZE);
buffer = (unsigned char*) malloc(BUFFER_SIZE);
auto remaining = pob.pob_length; auto remaining = pob.pob_length;
auto remaining_offset = pob.pob_offset; auto remaining_offset = pob.pob_offset;
tailer::hash_frag thf; tailer::hash_frag thf;

@ -42,7 +42,6 @@ void
xterm_mouse::handle_mouse() xterm_mouse::handle_mouse()
{ {
bool release = false; bool release = false;
int ch;
size_t index = 0; size_t index = 0;
int bstate, x, y; int bstate, x, y;
char buffer[64]; char buffer[64];
@ -52,7 +51,7 @@ xterm_mouse::handle_mouse()
if (index >= sizeof(buffer) - 1) { if (index >= sizeof(buffer) - 1) {
break; break;
} }
ch = getch(); auto ch = getch();
switch (ch) { switch (ch) {
case 'm': case 'm':
release = true; release = true;

@ -512,6 +512,7 @@ distclean-local:
$(RM_V)rm -rf remote remote-tmp not:a:remote:dir $(RM_V)rm -rf remote remote-tmp not:a:remote:dir
$(RM_V)rm -rf sessions $(RM_V)rm -rf sessions
$(RM_V)rm -rf tmp $(RM_V)rm -rf tmp
$(RM_V)rm -rf piper-tmp
$(RM_V)rm -rf rotmp $(RM_V)rm -rf rotmp
$(RM_V)rm -rf meta-sessions $(RM_V)rm -rf meta-sessions
$(RM_V)rm -rf mgmt-config $(RM_V)rm -rf mgmt-config

@ -89,7 +89,7 @@ public:
void grep_match(grep_proc<vis_line_t>& gp, void grep_match(grep_proc<vis_line_t>& gp,
vis_line_t line, vis_line_t line,
int start, int start,
int end) int end) override
{ {
printf("%d:%d:%d\n", (int) line, start, end); printf("%d:%d:%d\n", (int) line, start, end);
} }
@ -98,12 +98,21 @@ public:
vis_line_t line, vis_line_t line,
int start, int start,
int end, int end,
char* capture) const string_fragment& capture) override
{ {
fprintf(stderr, "%d(%d:%d)%s\n", (int) line, start, end, capture); fprintf(stderr,
"%d(%d:%d)%.*s\n",
(int) line,
start,
end,
capture.length(),
capture.data());
} }
void grep_end(grep_proc<vis_line_t>& gp) { this->ms_finished = true; } void grep_end(grep_proc<vis_line_t>& gp) override
{
this->ms_finished = true;
}
bool ms_finished; bool ms_finished;
}; };

@ -2,10 +2,18 @@
EXPECTED_FILES = \ EXPECTED_FILES = \
$(srcdir)/%reldir%/test_cli.sh_0b3639753916f71254e8c9cce4ebb8bfd9978d3e.err \ $(srcdir)/%reldir%/test_cli.sh_0b3639753916f71254e8c9cce4ebb8bfd9978d3e.err \
$(srcdir)/%reldir%/test_cli.sh_0b3639753916f71254e8c9cce4ebb8bfd9978d3e.out \ $(srcdir)/%reldir%/test_cli.sh_0b3639753916f71254e8c9cce4ebb8bfd9978d3e.out \
$(srcdir)/%reldir%/test_cli.sh_10c33e465ef7681c6b5519d05d557426b26cd43d.err \
$(srcdir)/%reldir%/test_cli.sh_10c33e465ef7681c6b5519d05d557426b26cd43d.out \
$(srcdir)/%reldir%/test_cli.sh_17a68b798354f9a6cdfab372006caeb74038d15c.err \ $(srcdir)/%reldir%/test_cli.sh_17a68b798354f9a6cdfab372006caeb74038d15c.err \
$(srcdir)/%reldir%/test_cli.sh_17a68b798354f9a6cdfab372006caeb74038d15c.out \ $(srcdir)/%reldir%/test_cli.sh_17a68b798354f9a6cdfab372006caeb74038d15c.out \
$(srcdir)/%reldir%/test_cli.sh_3114508cf42fb2608ef77f4bc294a84885c97a79.err \
$(srcdir)/%reldir%/test_cli.sh_3114508cf42fb2608ef77f4bc294a84885c97a79.out \
$(srcdir)/%reldir%/test_cli.sh_4327033cfae0d4c170a38a3c4a570520bfabb493.err \
$(srcdir)/%reldir%/test_cli.sh_4327033cfae0d4c170a38a3c4a570520bfabb493.out \
$(srcdir)/%reldir%/test_cli.sh_5524542b1a6954ff9741155101497270a2f0c557.err \ $(srcdir)/%reldir%/test_cli.sh_5524542b1a6954ff9741155101497270a2f0c557.err \
$(srcdir)/%reldir%/test_cli.sh_5524542b1a6954ff9741155101497270a2f0c557.out \ $(srcdir)/%reldir%/test_cli.sh_5524542b1a6954ff9741155101497270a2f0c557.out \
$(srcdir)/%reldir%/test_cli.sh_76aa57821598962e59063a40c20171040c95a731.err \
$(srcdir)/%reldir%/test_cli.sh_76aa57821598962e59063a40c20171040c95a731.out \
$(srcdir)/%reldir%/test_cli.sh_97e19b9ff3775d84074455a2e8993a0611b1c269.err \ $(srcdir)/%reldir%/test_cli.sh_97e19b9ff3775d84074455a2e8993a0611b1c269.err \
$(srcdir)/%reldir%/test_cli.sh_97e19b9ff3775d84074455a2e8993a0611b1c269.out \ $(srcdir)/%reldir%/test_cli.sh_97e19b9ff3775d84074455a2e8993a0611b1c269.out \
$(srcdir)/%reldir%/test_cli.sh_c69c835a3c43210225cf62564b3e9584c899af20.err \ $(srcdir)/%reldir%/test_cli.sh_c69c835a3c43210225cf62564b3e9584c899af20.err \
@ -146,8 +154,6 @@ EXPECTED_FILES = \
$(srcdir)/%reldir%/test_cmds.sh_95beaabe41d72cf4c6810e79c623da759ac1c71b.out \ $(srcdir)/%reldir%/test_cmds.sh_95beaabe41d72cf4c6810e79c623da759ac1c71b.out \
$(srcdir)/%reldir%/test_cmds.sh_968dac54dc80d91a5da2322890c6c26dfa0d8462.err \ $(srcdir)/%reldir%/test_cmds.sh_968dac54dc80d91a5da2322890c6c26dfa0d8462.err \
$(srcdir)/%reldir%/test_cmds.sh_968dac54dc80d91a5da2322890c6c26dfa0d8462.out \ $(srcdir)/%reldir%/test_cmds.sh_968dac54dc80d91a5da2322890c6c26dfa0d8462.out \
$(srcdir)/%reldir%/test_cmds.sh_9dfc433f14b811afb3ec4daef0f33e4c9b14a6d7.err \
$(srcdir)/%reldir%/test_cmds.sh_9dfc433f14b811afb3ec4daef0f33e4c9b14a6d7.out \
$(srcdir)/%reldir%/test_cmds.sh_a00943ef715598c7554b85de8502454e41bb9e28.err \ $(srcdir)/%reldir%/test_cmds.sh_a00943ef715598c7554b85de8502454e41bb9e28.err \
$(srcdir)/%reldir%/test_cmds.sh_a00943ef715598c7554b85de8502454e41bb9e28.out \ $(srcdir)/%reldir%/test_cmds.sh_a00943ef715598c7554b85de8502454e41bb9e28.out \
$(srcdir)/%reldir%/test_cmds.sh_a1123427c31c022433d66d05ee5d5e1c8ab415e4.err \ $(srcdir)/%reldir%/test_cmds.sh_a1123427c31c022433d66d05ee5d5e1c8ab415e4.err \

@ -6,7 +6,8 @@
}, },
"piper": { "piper": {
"max-size": 10485760, "max-size": 10485760,
"rotations": 4 "rotations": 4,
"ttl": "2d"
}, },
"file-vtab": { "file-vtab": {
"max-content-size": 33554432 "max-content-size": 33554432
@ -106,6 +107,9 @@
}, },
"hw": { "hw": {
"handler": "hw-url-handler" "handler": "hw-url-handler"
},
"piper": {
"handler": "piper-url-handler"
} }
} }
}, },

@ -0,0 +1,4 @@
ⓘ info: the following piper captures were found in:
piper-tmp/lnav-user-501-work/piper
 = note: The captures currently consume 31B of disk space. File sizes include associated metadata.
 = help: You can reopen a capture by passing the piper URL to lnav

@ -0,0 +1 @@
 just now piper://p-e25e2eb68547f31e42da0818b4d0084f-000  31.0 B “[0] echo hi”

@ -0,0 +1,9 @@
[
{
"filepath": "[0] echo hi",
"descriptor": "org.lnav.piper.header",
"mimetype": "application/json",
"ctime": "2013-06-06T19:13:20.000",
"cwd": "{test_dir}"
}
]

@ -1,4 +1,4 @@
Feb 25 16:20:12 192.168.4.2 haproxy[7]: 95.216.197.33:56224 [25/Feb/2019:16:20:10.111] prod_http_in/sktst2: SSL handshake failure Feb 25 16:20:15 192.168.4.2 haproxy[7]: 95.216.197.33:56224 [25/Feb/2019:16:20:10.111] prod_http_in/sktst2: SSL handshake failure
Feb 25 16:20:12 192.168.4.2 haproxy[7]: 87.183.41.77:50189 [25/Feb/2019:16:20:12.331] prod_http_in~ bk_ktest_sonst/nginx_sonst 0/0/1/0/1 200 2496 - - ---- 9/9/0/0/0 0/0 {Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0} {} "GET /media/core/core.css?1550939640 HTTP/1.1" Feb 25 16:20:16 192.168.4.2 haproxy[7]: 87.183.41.77:50188 [25/Feb/2019:16:20:12.321] prod_http_in~ bk_ktest_sonst/nginx_sonst 0/0/1/0/1 200 5959 - - ---- 9/9/0/0/0 0/0 {Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0} {} "GET /media/pi_fontawesome/css/font-awesome.css?1550939694 HTTP/1.1"
Feb 25 16:20:12 192.168.4.2 haproxy[7]: 87.183.41.77:50187 [25/Feb/2019:16:20:12.325] prod_http_in~ bk_ktest_sonst/nginx_sonst 0/0/1/0/1 200 1859 - - ---- 9/9/0/0/0 0/0 {Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0} {} "GET /media/pi_popup/1.1.0/magnific-popup.css?1550939704 HTTP/1.1" Feb 25 16:20:17 192.168.4.2 haproxy[7]: 87.183.41.77:50187 [25/Feb/2019:16:20:12.325] prod_http_in~ bk_ktest_sonst/nginx_sonst 0/0/1/0/1 200 1859 - - ---- 9/9/0/0/0 0/0 {Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0} {} "GET /media/pi_popup/1.1.0/magnific-popup.css?1550939704 HTTP/1.1"
Feb 25 16:20:12 192.168.4.2 haproxy[7]: 87.183.41.77:50188 [25/Feb/2019:16:20:12.321] prod_http_in~ bk_ktest_sonst/nginx_sonst 0/0/1/0/1 200 5959 - - ---- 9/9/0/0/0 0/0 {Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0} {} "GET /media/pi_fontawesome/css/font-awesome.css?1550939694 HTTP/1.1" Feb 25 16:20:18 192.168.4.2 haproxy[7]: 87.183.41.77:50189 [25/Feb/2019:16:20:12.331] prod_http_in~ bk_ktest_sonst/nginx_sonst 0/0/1/0/1 200 2496 - - ---- 9/9/0/0/0 0/0 {Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0} {} "GET /media/core/core.css?1550939640 HTTP/1.1"

@ -13,34 +13,36 @@
/global/keymap_def_zoom -> default-keymap.json:12 /global/keymap_def_zoom -> default-keymap.json:12
/tuning/archive-manager/cache-ttl -> root-config.json:16 /tuning/archive-manager/cache-ttl -> root-config.json:16
/tuning/archive-manager/min-free-space -> root-config.json:15 /tuning/archive-manager/min-free-space -> root-config.json:15
/tuning/clipboard/impls/MacOS/find/read -> root-config.json:43 /tuning/clipboard/impls/MacOS/find/read -> root-config.json:44
/tuning/clipboard/impls/MacOS/find/write -> root-config.json:42 /tuning/clipboard/impls/MacOS/find/write -> root-config.json:43
/tuning/clipboard/impls/MacOS/general/read -> root-config.json:39 /tuning/clipboard/impls/MacOS/general/read -> root-config.json:40
/tuning/clipboard/impls/MacOS/general/write -> root-config.json:38 /tuning/clipboard/impls/MacOS/general/write -> root-config.json:39
/tuning/clipboard/impls/MacOS/test -> root-config.json:36 /tuning/clipboard/impls/MacOS/test -> root-config.json:37
/tuning/clipboard/impls/NeoVim/general/read -> root-config.json:71 /tuning/clipboard/impls/NeoVim/general/read -> root-config.json:72
/tuning/clipboard/impls/NeoVim/general/write -> root-config.json:70 /tuning/clipboard/impls/NeoVim/general/write -> root-config.json:71
/tuning/clipboard/impls/NeoVim/test -> root-config.json:68 /tuning/clipboard/impls/NeoVim/test -> root-config.json:69
/tuning/clipboard/impls/Wayland/general/read -> root-config.json:50 /tuning/clipboard/impls/Wayland/general/read -> root-config.json:51
/tuning/clipboard/impls/Wayland/general/write -> root-config.json:49 /tuning/clipboard/impls/Wayland/general/write -> root-config.json:50
/tuning/clipboard/impls/Wayland/test -> root-config.json:47 /tuning/clipboard/impls/Wayland/test -> root-config.json:48
/tuning/clipboard/impls/Windows/general/write -> root-config.json:77 /tuning/clipboard/impls/Windows/general/write -> root-config.json:78
/tuning/clipboard/impls/Windows/test -> root-config.json:75 /tuning/clipboard/impls/Windows/test -> root-config.json:76
/tuning/clipboard/impls/X11-xclip/general/read -> root-config.json:57 /tuning/clipboard/impls/X11-xclip/general/read -> root-config.json:58
/tuning/clipboard/impls/X11-xclip/general/write -> root-config.json:56 /tuning/clipboard/impls/X11-xclip/general/write -> root-config.json:57
/tuning/clipboard/impls/X11-xclip/test -> root-config.json:54 /tuning/clipboard/impls/X11-xclip/test -> root-config.json:55
/tuning/clipboard/impls/tmux/general/read -> root-config.json:64 /tuning/clipboard/impls/tmux/general/read -> root-config.json:65
/tuning/clipboard/impls/tmux/general/write -> root-config.json:63 /tuning/clipboard/impls/tmux/general/write -> root-config.json:64
/tuning/clipboard/impls/tmux/test -> root-config.json:61 /tuning/clipboard/impls/tmux/test -> root-config.json:62
/tuning/piper/max-size -> root-config.json:30 /tuning/piper/max-size -> root-config.json:30
/tuning/piper/rotations -> root-config.json:31 /tuning/piper/rotations -> root-config.json:31
/tuning/piper/ttl -> root-config.json:32
/tuning/remote/ssh/command -> root-config.json:20 /tuning/remote/ssh/command -> root-config.json:20
/tuning/remote/ssh/config/BatchMode -> root-config.json:22 /tuning/remote/ssh/config/BatchMode -> root-config.json:22
/tuning/remote/ssh/config/ConnectTimeout -> root-config.json:23 /tuning/remote/ssh/config/ConnectTimeout -> root-config.json:23
/tuning/remote/ssh/start-command -> root-config.json:25 /tuning/remote/ssh/start-command -> root-config.json:25
/tuning/remote/ssh/transfer-command -> root-config.json:26 /tuning/remote/ssh/transfer-command -> root-config.json:26
/tuning/url-scheme/docker/handler -> root-config.json:84 /tuning/url-scheme/docker/handler -> root-config.json:85
/tuning/url-scheme/hw/handler -> {test_dir}/configs/installed/hw-url-handler.json:6 /tuning/url-scheme/hw/handler -> {test_dir}/configs/installed/hw-url-handler.json:6
/tuning/url-scheme/piper/handler -> root-config.json:88
/ui/clock-format -> root-config.json:4 /ui/clock-format -> root-config.json:4
/ui/default-colors -> root-config.json:6 /ui/default-colors -> root-config.json:6
/ui/dim-text -> root-config.json:5 /ui/dim-text -> root-config.json:5

@ -9,9 +9,9 @@ Feb 25 16:20:04 192.168.4.2 haproxy[7]: 141.35.244.171:53337 [25/Feb/2019:16:20:
Feb 25 16:20:09 192.168.4.2 haproxy[7]: 89.247.124.65:15564 [25/Feb/2019:16:20:06.321] prod_http_in~ bk_ktest_kt/nginx_sonst 0/0/1/43/2707 200 26170 - - ---- 3/3/1/1/0 0/0 {Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0} {} "GET /portal/?Script=934&onlinetest=korrektur&anm=13915&currentPage=0 HTTP/1.1" Feb 25 16:20:09 192.168.4.2 haproxy[7]: 89.247.124.65:15564 [25/Feb/2019:16:20:06.321] prod_http_in~ bk_ktest_kt/nginx_sonst 0/0/1/43/2707 200 26170 - - ---- 3/3/1/1/0 0/0 {Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0} {} "GET /portal/?Script=934&onlinetest=korrektur&anm=13915&currentPage=0 HTTP/1.1"
Feb 25 16:20:11 192.168.4.2 haproxy[7]: 89.247.124.65:15565 [25/Feb/2019:16:20:08.872] prod_http_in~ bk_ktest_kt/nginx_sonst 0/0/1/26/2442 200 26170 - - ---- 3/2/1/1/0 0/0 {Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0} {} "GET /portal/?Script=934&onlinetest=korrektur&anm=13915&currentPage=0 HTTP/1.1" Feb 25 16:20:11 192.168.4.2 haproxy[7]: 89.247.124.65:15565 [25/Feb/2019:16:20:08.872] prod_http_in~ bk_ktest_kt/nginx_sonst 0/0/1/26/2442 200 26170 - - ---- 3/2/1/1/0 0/0 {Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0} {} "GET /portal/?Script=934&onlinetest=korrektur&anm=13915&currentPage=0 HTTP/1.1"
Feb 25 16:20:12 192.168.4.2 haproxy[7]: 87.183.41.77:50186 [25/Feb/2019:16:20:11.910] prod_http_in~ bk_ktest_kt/nginx_sonst 0/0/0/236/236 200 4586 - - ---- 4/4/3/3/0 0/0 {Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0} {} "GET /portal/?Script=934&lehrer=77798 HTTP/1.1" Feb 25 16:20:12 192.168.4.2 haproxy[7]: 87.183.41.77:50186 [25/Feb/2019:16:20:11.910] prod_http_in~ bk_ktest_kt/nginx_sonst 0/0/0/236/236 200 4586 - - ---- 4/4/3/3/0 0/0 {Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0} {} "GET /portal/?Script=934&lehrer=77798 HTTP/1.1"
Feb 25 16:20:12 192.168.4.2 haproxy[7]: 87.183.41.77:50186 [25/Feb/2019:16:20:12.234] prod_http_in~ bk_ktest_sonst/nginx_sonst 0/0/1/0/1 200 16416 - - ---- 4/4/0/0/0 0/0 {Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0} {} "GET /media/core/bootstrap_3.3.7/css/bootstrap.css?1550939643 HTTP/1.1" Feb 25 16:20:13 192.168.4.2 haproxy[7]: 87.183.41.77:50186 [25/Feb/2019:16:20:13.234] prod_http_in~ bk_ktest_sonst/nginx_sonst 0/0/1/0/1 200 16416 - - ---- 4/4/0/0/0 0/0 {Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0} {} "GET /media/core/bootstrap_3.3.7/css/bootstrap.css?1550939643 HTTP/1.1"
Feb 25 16:20:12 192.168.4.2 haproxy[7]: 87.183.41.77:50186 [25/Feb/2019:16:20:12.317] prod_http_in~ bk_ktest_sonst/nginx_sonst 0/0/0/1/1 200 11065 - - ---- 9/9/0/0/0 0/0 {Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0} {} "GET /media/core/jquery/jquery-ui-1.12.1.js?1550939557 HTTP/1.1" Feb 25 16:20:14 192.168.4.2 haproxy[7]: 87.183.41.77:50186 [25/Feb/2019:16:20:14.317] prod_http_in~ bk_ktest_sonst/nginx_sonst 0/0/0/1/1 200 11065 - - ---- 9/9/0/0/0 0/0 {Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0} {} "GET /media/core/jquery/jquery-ui-1.12.1.js?1550939557 HTTP/1.1"
Feb 25 16:20:12 192.168.4.2 haproxy[7]: 95.216.197.33:56224 [25/Feb/2019:16:20:10.111] prod_http_in/sktst2: SSL handshake failure Feb 25 16:20:15 192.168.4.2 haproxy[7]: 95.216.197.33:56224 [25/Feb/2019:16:20:10.111] prod_http_in/sktst2: SSL handshake failure
Feb 25 16:20:12 192.168.4.2 haproxy[7]: 87.183.41.77:50188 [25/Feb/2019:16:20:12.321] prod_http_in~ bk_ktest_sonst/nginx_sonst 0/0/1/0/1 200 5959 - - ---- 9/9/0/0/0 0/0 {Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0} {} "GET /media/pi_fontawesome/css/font-awesome.css?1550939694 HTTP/1.1" Feb 25 16:20:16 192.168.4.2 haproxy[7]: 87.183.41.77:50188 [25/Feb/2019:16:20:12.321] prod_http_in~ bk_ktest_sonst/nginx_sonst 0/0/1/0/1 200 5959 - - ---- 9/9/0/0/0 0/0 {Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0} {} "GET /media/pi_fontawesome/css/font-awesome.css?1550939694 HTTP/1.1"
Feb 25 16:20:12 192.168.4.2 haproxy[7]: 87.183.41.77:50187 [25/Feb/2019:16:20:12.325] prod_http_in~ bk_ktest_sonst/nginx_sonst 0/0/1/0/1 200 1859 - - ---- 9/9/0/0/0 0/0 {Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0} {} "GET /media/pi_popup/1.1.0/magnific-popup.css?1550939704 HTTP/1.1" Feb 25 16:20:17 192.168.4.2 haproxy[7]: 87.183.41.77:50187 [25/Feb/2019:16:20:12.325] prod_http_in~ bk_ktest_sonst/nginx_sonst 0/0/1/0/1 200 1859 - - ---- 9/9/0/0/0 0/0 {Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0} {} "GET /media/pi_popup/1.1.0/magnific-popup.css?1550939704 HTTP/1.1"
Feb 25 16:20:12 192.168.4.2 haproxy[7]: 87.183.41.77:50189 [25/Feb/2019:16:20:12.331] prod_http_in~ bk_ktest_sonst/nginx_sonst 0/0/1/0/1 200 2496 - - ---- 9/9/0/0/0 0/0 {Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0} {} "GET /media/core/core.css?1550939640 HTTP/1.1" Feb 25 16:20:18 192.168.4.2 haproxy[7]: 87.183.41.77:50189 [25/Feb/2019:16:20:12.331] prod_http_in~ bk_ktest_sonst/nginx_sonst 0/0/1/0/1 200 2496 - - ---- 9/9/0/0/0 0/0 {Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0} {} "GET /media/core/core.css?1550939640 HTTP/1.1"

@ -45,3 +45,17 @@ mkdir -p $HOME/.lnav
run_cap_test ${lnav_test} -m -I ${test_dir} config get run_cap_test ${lnav_test} -m -I ${test_dir} config get
run_cap_test ${lnav_test} -m -I ${test_dir} config blame run_cap_test ${lnav_test} -m -I ${test_dir} config blame
export TMPDIR="piper-tmp"
rm -rf ./piper-tmp
run_cap_test ${lnav_test} -n -e 'echo hi'
run_cap_test ${lnav_test} -m piper list
PIPER_URL=$(${lnav_test} -m -q piper list | tail -1 | sed -r -e 's;.*(piper://[^ ]+).*;\1;g')
run_cap_test ${lnav_test} -n $PIPER_URL
run_cap_test ${lnav_test} -n $PIPER_URL \
-c ";SELECT filepath, descriptor, mimetype, jget(content, '/ctime') as ctime, jget(content, '/cwd') as cwd FROM lnav_file_metadata" \
-c ':write-json-to -'

Loading…
Cancel
Save