[fstat_vtab] add error column

This commit is contained in:
Tim Stack 2023-08-30 06:55:24 -07:00
parent 63e5b81002
commit b561dd18c4
19 changed files with 178 additions and 68 deletions

View File

@ -41,6 +41,14 @@
namespace lnav {
namespace filesystem {
inline bool
is_glob(const std::string& fn)
{
return (fn.find('*') != std::string::npos
|| fn.find('?') != std::string::npos
|| fn.find('[') != std::string::npos);
}
inline int
statp(const ghc::filesystem::path& path, struct stat* buf)
{

View File

@ -64,6 +64,7 @@ enum {
FSTAT_COL_ATIME,
FSTAT_COL_MTIME,
FSTAT_COL_CTIME,
FSTAT_COL_ERROR,
FSTAT_COL_PATTERN,
FSTAT_COL_DATA,
};
@ -93,6 +94,7 @@ CREATE TABLE fstat (
st_atime DATETIME,
st_mtime DATETIME,
st_ctime DATETIME,
error TEXT,
pattern TEXT HIDDEN,
data BLOB HIDDEN
);
@ -104,20 +106,20 @@ CREATE TABLE fstat (
static_root_mem<glob_t, globfree> c_glob;
size_t c_path_index{0};
struct stat c_stat;
std::string c_error;
cursor(sqlite3_vtab* vt) : base({vt})
explicit cursor(sqlite3_vtab* vt) : base({vt})
{
memset(&this->c_stat, 0, sizeof(this->c_stat));
}
void load_stat()
{
while ((this->c_path_index < this->c_glob->gl_pathc)
&& lstat(this->c_glob->gl_pathv[this->c_path_index],
&this->c_stat)
== -1)
{
this->c_path_index += 1;
auto rc = lstat(this->c_glob->gl_pathv[this->c_path_index],
&this->c_stat);
if (rc == -1) {
this->c_error = strerror(errno);
}
}
@ -173,13 +175,23 @@ CREATE TABLE fstat (
break;
}
case FSTAT_COL_DEV:
sqlite3_result_int(ctx, vc.c_stat.st_dev);
if (vc.c_error.empty()) {
sqlite3_result_int(ctx, vc.c_stat.st_dev);
} else {
sqlite3_result_null(ctx);
}
break;
case FSTAT_COL_INO:
sqlite3_result_int64(ctx, vc.c_stat.st_ino);
if (vc.c_error.empty()) {
sqlite3_result_int64(ctx, vc.c_stat.st_ino);
} else {
sqlite3_result_null(ctx);
}
break;
case FSTAT_COL_TYPE:
if (S_ISREG(vc.c_stat.st_mode)) {
if (!vc.c_error.empty()) {
sqlite3_result_null(ctx);
} else if (S_ISREG(vc.c_stat.st_mode)) {
sqlite3_result_text(ctx, "reg", 3, SQLITE_STATIC);
} else if (S_ISBLK(vc.c_stat.st_mode)) {
sqlite3_result_text(ctx, "blk", 3, SQLITE_STATIC);
@ -196,60 +208,124 @@ CREATE TABLE fstat (
}
break;
case FSTAT_COL_MODE:
sqlite3_result_int(ctx, vc.c_stat.st_mode & 0777);
if (vc.c_error.empty()) {
sqlite3_result_int(ctx, vc.c_stat.st_mode & 0777);
} else {
sqlite3_result_null(ctx);
}
break;
case FSTAT_COL_NLINK:
sqlite3_result_int(ctx, vc.c_stat.st_nlink);
if (vc.c_error.empty()) {
sqlite3_result_int(ctx, vc.c_stat.st_nlink);
} else {
sqlite3_result_null(ctx);
}
break;
case FSTAT_COL_UID:
sqlite3_result_int(ctx, vc.c_stat.st_uid);
if (vc.c_error.empty()) {
sqlite3_result_int(ctx, vc.c_stat.st_uid);
} else {
sqlite3_result_null(ctx);
}
break;
case FSTAT_COL_USER: {
struct passwd* pw = getpwuid(vc.c_stat.st_uid);
if (vc.c_error.empty()) {
struct passwd* pw = getpwuid(vc.c_stat.st_uid);
if (pw != nullptr) {
sqlite3_result_text(ctx, pw->pw_name, -1, SQLITE_TRANSIENT);
if (pw != nullptr) {
sqlite3_result_text(
ctx, pw->pw_name, -1, SQLITE_TRANSIENT);
} else {
sqlite3_result_int(ctx, vc.c_stat.st_uid);
}
} else {
sqlite3_result_int(ctx, vc.c_stat.st_uid);
sqlite3_result_null(ctx);
}
break;
}
case FSTAT_COL_GID:
sqlite3_result_int(ctx, vc.c_stat.st_gid);
if (vc.c_error.empty()) {
sqlite3_result_int(ctx, vc.c_stat.st_gid);
} else {
sqlite3_result_null(ctx);
}
break;
case FSTAT_COL_GROUP: {
struct group* gr = getgrgid(vc.c_stat.st_gid);
if (vc.c_error.empty()) {
struct group* gr = getgrgid(vc.c_stat.st_gid);
if (gr != nullptr) {
sqlite3_result_text(ctx, gr->gr_name, -1, SQLITE_TRANSIENT);
if (gr != nullptr) {
sqlite3_result_text(
ctx, gr->gr_name, -1, SQLITE_TRANSIENT);
} else {
sqlite3_result_int(ctx, vc.c_stat.st_gid);
}
} else {
sqlite3_result_int(ctx, vc.c_stat.st_gid);
sqlite3_result_null(ctx);
}
break;
}
case FSTAT_COL_RDEV:
sqlite3_result_int(ctx, vc.c_stat.st_rdev);
if (vc.c_error.empty()) {
sqlite3_result_int(ctx, vc.c_stat.st_rdev);
} else {
sqlite3_result_null(ctx);
}
break;
case FSTAT_COL_SIZE:
sqlite3_result_int64(ctx, vc.c_stat.st_size);
if (vc.c_error.empty()) {
sqlite3_result_int64(ctx, vc.c_stat.st_size);
} else {
sqlite3_result_null(ctx);
}
break;
case FSTAT_COL_BLKSIZE:
sqlite3_result_int(ctx, vc.c_stat.st_blksize);
if (vc.c_error.empty()) {
sqlite3_result_int(ctx, vc.c_stat.st_blksize);
} else {
sqlite3_result_null(ctx);
}
break;
case FSTAT_COL_BLOCKS:
sqlite3_result_int(ctx, vc.c_stat.st_blocks);
if (vc.c_error.empty()) {
sqlite3_result_int(ctx, vc.c_stat.st_blocks);
} else {
sqlite3_result_null(ctx);
}
break;
case FSTAT_COL_ATIME:
sql_strftime(time_buf, sizeof(time_buf), vc.c_stat.st_atime, 0);
sqlite3_result_text(ctx, time_buf, -1, SQLITE_TRANSIENT);
if (vc.c_error.empty()) {
sql_strftime(
time_buf, sizeof(time_buf), vc.c_stat.st_atime, 0);
sqlite3_result_text(ctx, time_buf, -1, SQLITE_TRANSIENT);
} else {
sqlite3_result_null(ctx);
}
break;
case FSTAT_COL_MTIME:
sql_strftime(time_buf, sizeof(time_buf), vc.c_stat.st_mtime, 0);
sqlite3_result_text(ctx, time_buf, -1, SQLITE_TRANSIENT);
if (vc.c_error.empty()) {
sql_strftime(
time_buf, sizeof(time_buf), vc.c_stat.st_mtime, 0);
sqlite3_result_text(ctx, time_buf, -1, SQLITE_TRANSIENT);
} else {
sqlite3_result_null(ctx);
}
break;
case FSTAT_COL_CTIME:
sql_strftime(time_buf, sizeof(time_buf), vc.c_stat.st_ctime, 0);
sqlite3_result_text(ctx, time_buf, -1, SQLITE_TRANSIENT);
if (vc.c_error.empty()) {
sql_strftime(
time_buf, sizeof(time_buf), vc.c_stat.st_ctime, 0);
sqlite3_result_text(ctx, time_buf, -1, SQLITE_TRANSIENT);
} else {
sqlite3_result_null(ctx);
}
break;
case FSTAT_COL_ERROR:
if (vc.c_error.empty()) {
sqlite3_result_null(ctx);
} else {
to_sqlite(ctx, vc.c_error);
}
break;
case FSTAT_COL_PATTERN:
sqlite3_result_text(ctx,
@ -259,7 +335,9 @@ CREATE TABLE fstat (
break;
case FSTAT_COL_DATA: {
auto fs_path = ghc::filesystem::path{path};
if (S_ISREG(vc.c_stat.st_mode)) {
if (!vc.c_error.empty()) {
sqlite3_result_null(ctx);
} else if (S_ISREG(vc.c_stat.st_mode)) {
auto open_res
= lnav::filesystem::open_file(fs_path, O_RDONLY);
@ -366,14 +444,17 @@ rcFilter(sqlite3_vtab_cursor* pVtabCursor,
const char* pattern = (const char*) sqlite3_value_text(argv[0]);
pCur->c_pattern = pattern;
switch (glob(pattern,
auto glob_flags = GLOB_ERR;
if (!lnav::filesystem::is_glob(pCur->c_pattern)) {
glob_flags |= GLOB_NOCHECK;
}
#ifdef GLOB_TILDE
GLOB_TILDE |
glob_flags |= GLOB_TILDE;
#endif
GLOB_ERR,
nullptr,
pCur->c_glob.inout()))
{
switch (glob(pattern, glob_flags, nullptr, pCur->c_glob.inout())) {
case GLOB_NOSPACE:
pVtabCursor->pVtab->zErrMsg
= sqlite3_mprintf("No space to perform glob()");

View File

@ -3127,13 +3127,14 @@ radians(*degrees*)
.. _raise_error:
raise_error(*msg*)
^^^^^^^^^^^^^^^^^^
raise_error(*msg*, *\[reason\]*)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Raises an error with the given message when executed
**Parameters**
* **msg\*** --- The error message
* **reason** --- The reason the error occurred
----

View File

@ -2955,7 +2955,7 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
fmt::format(FMT_STRING(":open {}"), file_path));
}
#endif
else if (is_glob(file_path))
else if (lnav::filesystem::is_glob(file_path))
{
lnav_data.ld_active_files.fc_file_names[file_path].with_tail(
!(lnav_data.ld_flags & LNF_HEADLESS));

View File

@ -2699,7 +2699,7 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
}
retval = "info: watching -- " + fn;
} else if (is_glob(fn.c_str())) {
} else if (lnav::filesystem::is_glob(fn.c_str())) {
fc.fc_file_names.emplace(fn, loo);
files_to_front.emplace_back(
loo.loo_filename.empty() ? fn : loo.loo_filename, file_loc);
@ -2827,7 +2827,7 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
}
});
lnav_data.ld_preview_view.set_needs_update();
} else if (is_glob(fn.c_str())) {
} else if (lnav::filesystem::is_glob(fn.c_str())) {
static_root_mem<glob_t, globfree> gl;
if (glob(fn.c_str(), GLOB_NOCHECK, nullptr, gl.inout()) == 0) {

View File

@ -81,14 +81,6 @@ to_string(const char* s)
}
} // namespace std
inline bool
is_glob(const std::string& fn)
{
return (fn.find('*') != std::string::npos
|| fn.find('?') != std::string::npos
|| fn.find('[') != std::string::npos);
}
inline void
rusagesub(const struct rusage& left,
const struct rusage& right,

View File

@ -7,6 +7,8 @@
jget(url, '/path') AS docker_path
FROM (SELECT parse_url($1) AS url)
;SELECT substr($docker_path, 2) AS docker_relpath
;SELECT CASE
$docker_hostname
WHEN 'compose' THEN (
@ -18,8 +20,19 @@
),
char(10)
) AS cmds
FROM fstat(substr($docker_path, 2)),
json_each(yaml_to_json(data), '$.services') as compose_services
FROM fstat($docker_relpath) AS st,
json_each(
yaml_to_json(
ifnull(
st.data,
raise_error(
'Cannot read compose configuration: ' || $docker_relpath,
st.error
)
)
),
'$.services'
) as compose_services
)
ELSE CASE
$docker_path

View File

@ -99,9 +99,14 @@ sql_lnav_version()
}
static int64_t
sql_error(const char* str)
sql_error(const char* str, nonstd::optional<string_fragment> reason)
{
throw sqlite_func_error("{}", str);
auto um = lnav::console::user_message::error(str);
if (reason) {
um.with_reason(reason->to_string());
}
throw um;
}
static nonstd::optional<std::string>
@ -155,7 +160,10 @@ state_extension_functions(struct FuncDef** basic_funcs,
help_text("raise_error",
"Raises an error with the given message when executed")
.sql_function()
.with_parameter({"msg", "The error message"}))
.with_parameter({"msg", "The error message"})
.with_parameter(
help_text("reason", "The reason the error occurred")
.optional()))
.with_flags(SQLITE_UTF8),
sqlite_func_adapter<decltype(&sql_echoln), sql_echoln>::builder(

View File

@ -736,6 +736,8 @@ EXPECTED_FILES = \
$(srcdir)/%reldir%/test_sql_fs_func.sh_18ddc138b263dd06f3fe81fec05bc4330caffef7.out \
$(srcdir)/%reldir%/test_sql_fs_func.sh_20a76db446a0a558dcbdf41033f97d4a22ca1bfa.err \
$(srcdir)/%reldir%/test_sql_fs_func.sh_20a76db446a0a558dcbdf41033f97d4a22ca1bfa.out \
$(srcdir)/%reldir%/test_sql_fs_func.sh_2aa83fc90c850cdd11e3136a1a02b79c5879824b.err \
$(srcdir)/%reldir%/test_sql_fs_func.sh_2aa83fc90c850cdd11e3136a1a02b79c5879824b.out \
$(srcdir)/%reldir%/test_sql_fs_func.sh_2c3f66e78deb8721b1d1fe5a787e9958895401d7.err \
$(srcdir)/%reldir%/test_sql_fs_func.sh_2c3f66e78deb8721b1d1fe5a787e9958895401d7.out \
$(srcdir)/%reldir%/test_sql_fs_func.sh_34baa8050f8278d7b68c29e53bdd9f37da0f34c8.err \
@ -756,6 +758,8 @@ EXPECTED_FILES = \
$(srcdir)/%reldir%/test_sql_fs_func.sh_7b5d7dd8d0003ab83e3e5cb0a5ce802fe9a0e3b3.out \
$(srcdir)/%reldir%/test_sql_fs_func.sh_917ffde411c1425e8a6addae0170900dcd553986.err \
$(srcdir)/%reldir%/test_sql_fs_func.sh_917ffde411c1425e8a6addae0170900dcd553986.out \
$(srcdir)/%reldir%/test_sql_fs_func.sh_9234a453403934587bbbdde355281a956d1fbe5f.err \
$(srcdir)/%reldir%/test_sql_fs_func.sh_9234a453403934587bbbdde355281a956d1fbe5f.out \
$(srcdir)/%reldir%/test_sql_fs_func.sh_9e2c0a90ce333365ff7354375f2c609bc27135c8.err \
$(srcdir)/%reldir%/test_sql_fs_func.sh_9e2c0a90ce333365ff7354375f2c609bc27135c8.out \
$(srcdir)/%reldir%/test_sql_fs_func.sh_a247b137e71124e496f1beab56c7fe85717c4199.err \

View File

@ -3759,11 +3759,12 @@ For support questions, email:
raise_error(msg)
raise_error(msg, [reason])
══════════════════════════════════════════════════════════════════════
Raises an error with the given message when executed
Parameter
msg The error message
Parameters
msg The error message
reason The reason the error occurred
random()

View File

@ -1,4 +1,3 @@
✘ error: call to raise_error(msg) failed
reason: oops!
✘ error: oops!
 --> command-option:1
 | ;SELECT raise_error('oops!') 

View File

@ -1,5 +1,4 @@
✘ error: call to raise_error(msg) failed
reason: no data was redirected to lnav's standard-input
✘ error: no data was redirected to lnav's standard-input
 --> command-option:1
 | |rename-stdin foo 
 --> ../test/.lnav/formats/default/rename-stdin.lnav:7

View File

@ -1,5 +1,4 @@
✘ error: call to raise_error(msg) failed
reason: expecting the new name for stdin as the first argument
✘ error: expecting the new name for stdin as the first argument
 --> command-option:1
 | |rename-stdin 
 --> ../test/.lnav/formats/default/rename-stdin.lnav:6

View File

@ -1,5 +1,4 @@
✘ error: call to raise_error(msg) failed
reason: oops!
✘ error: oops!
 --> command-option:2
 | ;SELECT $inum, $nul, $fnum, $str, raise_error('oops!')
 = note: the bound parameters are set as follows:

View File

@ -0,0 +1,2 @@
st_parent st_name st_dev st_ino st_type st_mode st_nlink st_uid st_user st_gid st_group st_rdev st_size st_blksize st_blocks st_atime st_mtime st_ctime error
/ non-existent <NULL> <NULL> <NULL> <NULL> <NULL> <NULL> <NULL> <NULL> <NULL> <NULL> <NULL> <NULL> <NULL> <NULL> <NULL> <NULL> No such file or directory

View File

@ -53,3 +53,7 @@ run_cap_test ./drive_sql "select joinpath('foo', 'bar', 'baz')"
run_cap_test ./drive_sql "select joinpath('foo', 'bar', 'baz', '/root')"
run_cap_test ${lnav_test} -Nn -c ";SELECT shell_exec('echo hi')"
run_cap_test ${lnav_test} -Nn -c ";SELECT * FROM fstat('/non-existent')"
run_cap_test ${lnav_test} -Nn -c ";SELECT * FROM fstat('/*.non-existent')"