From b4b08fc3f5719b1f0e85456eff4e9e55467dfa6f Mon Sep 17 00:00:00 2001 From: Tim Stack Date: Wed, 6 Mar 2024 09:17:53 -0800 Subject: [PATCH] [lnav -i] write file to install to temp file Related to #1240 --- src/base/fs_util.cc | 27 +++++- src/base/fs_util.hh | 15 +++- src/lnav.cc | 87 +++++++++++-------- test/expected/expected.am | 6 ++ ...08efc3b8c7b67d944a1f8c475cd31d98d5b4f6.err | 2 + ...08efc3b8c7b67d944a1f8c475cd31d98d5b4f6.out | 0 ...d73153a8990b8ddb8ce489e90ec667a442f7f9.err | 0 ...d73153a8990b8ddb8ce489e90ec667a442f7f9.out | 2 + ...7cbc64a150c7fe2a17e1c7a69e9a932aeaa16b.err | 0 ...7cbc64a150c7fe2a17e1c7a69e9a932aeaa16b.out | 1 + test/test_format_installer.sh | 14 +++ 11 files changed, 115 insertions(+), 39 deletions(-) create mode 100644 test/expected/test_format_installer.sh_1e08efc3b8c7b67d944a1f8c475cd31d98d5b4f6.err create mode 100644 test/expected/test_format_installer.sh_1e08efc3b8c7b67d944a1f8c475cd31d98d5b4f6.out create mode 100644 test/expected/test_format_installer.sh_6cd73153a8990b8ddb8ce489e90ec667a442f7f9.err create mode 100644 test/expected/test_format_installer.sh_6cd73153a8990b8ddb8ce489e90ec667a442f7f9.out create mode 100644 test/expected/test_format_installer.sh_947cbc64a150c7fe2a17e1c7a69e9a932aeaa16b.err create mode 100644 test/expected/test_format_installer.sh_947cbc64a150c7fe2a17e1c7a69e9a932aeaa16b.out diff --git a/src/base/fs_util.cc b/src/base/fs_util.cc index b08384cc..904d1c5e 100644 --- a/src/base/fs_util.cc +++ b/src/base/fs_util.cc @@ -117,9 +117,12 @@ read_file(const ghc::filesystem::path& path) } } -Result -write_file(const ghc::filesystem::path& path, const string_fragment& content) +Result +write_file(const ghc::filesystem::path& path, + const string_fragment& content, + std::set options) { + write_file_result retval; auto tmp_pattern = path; tmp_pattern += ".XXXXXX"; @@ -138,7 +141,25 @@ write_file(const ghc::filesystem::path& path, const string_fragment& content) bytes_written, content.length())); } + std::error_code ec; + if (options.count(write_file_options::backup_existing)) { + if (ghc::filesystem::exists(path, ec)) { + auto backup_path = path; + + backup_path += ".bak"; + ghc::filesystem::rename(path, backup_path, ec); + if (ec) { + return Err( + fmt::format(FMT_STRING("unable to backup file {}: {}"), + path.string(), + ec.message())); + } + + retval.wfr_backup_path = backup_path; + } + } + ghc::filesystem::rename(tmp_pair.first, path, ec); if (ec) { return Err( @@ -147,7 +168,7 @@ write_file(const ghc::filesystem::path& path, const string_fragment& content) ec.message())); } - return Ok(); + return Ok(retval); } std::string diff --git a/src/base/fs_util.hh b/src/base/fs_util.hh index 303c3b7c..1716d074 100644 --- a/src/base/fs_util.hh +++ b/src/base/fs_util.hh @@ -30,6 +30,7 @@ #ifndef lnav_fs_util_hh #define lnav_fs_util_hh +#include #include #include @@ -84,8 +85,18 @@ Result, std::string> open_temp_file( Result read_file(const ghc::filesystem::path& path); -Result write_file(const ghc::filesystem::path& path, - const string_fragment& content); +enum class write_file_options { + backup_existing, +}; + +struct write_file_result { + nonstd::optional wfr_backup_path; +}; + +Result write_file( + const ghc::filesystem::path& path, + const string_fragment& content, + std::set options = {}); std::string build_path(const std::vector& paths); diff --git a/src/lnav.cc b/src/lnav.cc index 154c7f92..8dfd8c01 100644 --- a/src/lnav.cc +++ b/src/lnav.cc @@ -2762,45 +2762,64 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%' ? configs_installed_path : formats_installed_path) / dst_name; - auto_fd in_fd, out_fd; - if ((in_fd = open(file_path.c_str(), O_RDONLY)) == -1) { - perror("unable to open file to install"); - } else if ((out_fd = lnav::filesystem::openp( - dst_path, O_WRONLY | O_CREAT | O_TRUNC, 0644)) - == -1) - { - fprintf(stderr, - "error: unable to open destination: %s -- %s\n", - dst_path.c_str(), - strerror(errno)); - } else { - char buffer[2048]; - ssize_t rc; - - while ((rc = read(in_fd, buffer, sizeof(buffer))) > 0) { - ssize_t remaining = rc, written; - - while (remaining > 0) { - written = write(out_fd, buffer, rc); - if (written == -1) { - fprintf(stderr, - "error: unable to install file " - "-- %s\n", - strerror(errno)); - exit(EXIT_FAILURE); - } + auto read_res = lnav::filesystem::read_file(file_path); + if (read_res.isErr()) { + auto um = lnav::console::user_message::error( + attr_line_t("cannot read file to install -- ") + .append(lnav::roles::file(file_path))) + .with_reason(read_res.unwrap()); - remaining -= written; - } + lnav::console::print(stderr, um); + return EXIT_FAILURE; + } + + auto file_content = read_res.unwrap(); + + auto read_dst_res = lnav::filesystem::read_file(dst_path); + if (read_dst_res.isOk()) { + auto dst_content = read_dst_res.unwrap(); + + if (dst_content == file_content) { + auto um = lnav::console::user_message::info( + attr_line_t("file is already installed at -- ") + .append(lnav::roles::file(dst_path))); + + lnav::console::print(stdout, um); + + return EXIT_SUCCESS; } + } - lnav::console::print( - stderr, - lnav::console::user_message::ok( - attr_line_t("installed -- ") - .append(lnav::roles::file(dst_path)))); + auto write_res = lnav::filesystem::write_file( + dst_path, + file_content, + {lnav::filesystem::write_file_options::backup_existing}); + if (write_res.isErr()) { + auto um = lnav::console::user_message::error( + attr_line_t("failed to install file to -- ") + .append(lnav::roles::file(dst_path))) + .with_reason(write_res.unwrapErr()); + + lnav::console::print(stderr, um); + return EXIT_FAILURE; } + + auto write_file_res = write_res.unwrap(); + auto um = lnav::console::user_message::ok( + attr_line_t("installed -- ") + .append(lnav::roles::file(dst_path))); + if (write_file_res.wfr_backup_path) { + um.with_note( + attr_line_t("the previously installed ") + .append_quoted( + lnav::roles::file(dst_path.filename().string())) + .append(" was backed up to -- ") + .append(lnav::roles::file( + write_file_res.wfr_backup_path.value().string()))); + } + + lnav::console::print(stdout, um); } return EXIT_SUCCESS; } diff --git a/test/expected/expected.am b/test/expected/expected.am index 9111efca..2fe141e1 100644 --- a/test/expected/expected.am +++ b/test/expected/expected.am @@ -278,6 +278,12 @@ EXPECTED_FILES = \ $(srcdir)/%reldir%/test_events.sh_d9c7907f907b2335e1328b23fdc46d0968a608d9.out \ $(srcdir)/%reldir%/test_events.sh_ed8dc44add223341c03ccb7b3e18371bdb42b710.err \ $(srcdir)/%reldir%/test_events.sh_ed8dc44add223341c03ccb7b3e18371bdb42b710.out \ + $(srcdir)/%reldir%/test_format_installer.sh_1e08efc3b8c7b67d944a1f8c475cd31d98d5b4f6.err \ + $(srcdir)/%reldir%/test_format_installer.sh_1e08efc3b8c7b67d944a1f8c475cd31d98d5b4f6.out \ + $(srcdir)/%reldir%/test_format_installer.sh_6cd73153a8990b8ddb8ce489e90ec667a442f7f9.err \ + $(srcdir)/%reldir%/test_format_installer.sh_6cd73153a8990b8ddb8ce489e90ec667a442f7f9.out \ + $(srcdir)/%reldir%/test_format_installer.sh_947cbc64a150c7fe2a17e1c7a69e9a932aeaa16b.err \ + $(srcdir)/%reldir%/test_format_installer.sh_947cbc64a150c7fe2a17e1c7a69e9a932aeaa16b.out \ $(srcdir)/%reldir%/test_format_loader.sh_15e861d2327512a721fd42ae51dc5427689e0bb6.err \ $(srcdir)/%reldir%/test_format_loader.sh_15e861d2327512a721fd42ae51dc5427689e0bb6.out \ $(srcdir)/%reldir%/test_format_loader.sh_5992e2695b7e6cf1f3520dbb87af8fc2b8f27088.err \ diff --git a/test/expected/test_format_installer.sh_1e08efc3b8c7b67d944a1f8c475cd31d98d5b4f6.err b/test/expected/test_format_installer.sh_1e08efc3b8c7b67d944a1f8c475cd31d98d5b4f6.err new file mode 100644 index 00000000..8b4f3e4c --- /dev/null +++ b/test/expected/test_format_installer.sh_1e08efc3b8c7b67d944a1f8c475cd31d98d5b4f6.err @@ -0,0 +1,2 @@ +✘ error: unable to open configuration file: /non-existent/file + reason: No such file or directory diff --git a/test/expected/test_format_installer.sh_1e08efc3b8c7b67d944a1f8c475cd31d98d5b4f6.out b/test/expected/test_format_installer.sh_1e08efc3b8c7b67d944a1f8c475cd31d98d5b4f6.out new file mode 100644 index 00000000..e69de29b diff --git a/test/expected/test_format_installer.sh_6cd73153a8990b8ddb8ce489e90ec667a442f7f9.err b/test/expected/test_format_installer.sh_6cd73153a8990b8ddb8ce489e90ec667a442f7f9.err new file mode 100644 index 00000000..e69de29b diff --git a/test/expected/test_format_installer.sh_6cd73153a8990b8ddb8ce489e90ec667a442f7f9.out b/test/expected/test_format_installer.sh_6cd73153a8990b8ddb8ce489e90ec667a442f7f9.out new file mode 100644 index 00000000..95b5acd6 --- /dev/null +++ b/test/expected/test_format_installer.sh_6cd73153a8990b8ddb8ce489e90ec667a442f7f9.out @@ -0,0 +1,2 @@ +✔ installed -- ../installer-test-home/.lnav/formats/installed/test_log.json + = note: the previously installed “test_log.json” was backed up to -- ../installer-test-home/.lnav/formats/installed/test_log.json.bak diff --git a/test/expected/test_format_installer.sh_947cbc64a150c7fe2a17e1c7a69e9a932aeaa16b.err b/test/expected/test_format_installer.sh_947cbc64a150c7fe2a17e1c7a69e9a932aeaa16b.err new file mode 100644 index 00000000..e69de29b diff --git a/test/expected/test_format_installer.sh_947cbc64a150c7fe2a17e1c7a69e9a932aeaa16b.out b/test/expected/test_format_installer.sh_947cbc64a150c7fe2a17e1c7a69e9a932aeaa16b.out new file mode 100644 index 00000000..8ac09f2f --- /dev/null +++ b/test/expected/test_format_installer.sh_947cbc64a150c7fe2a17e1c7a69e9a932aeaa16b.out @@ -0,0 +1 @@ +ⓘ info: file is already installed at -- ../installer-test-home/.lnav/formats/installed/test_log.json diff --git a/test/test_format_installer.sh b/test/test_format_installer.sh index 2b94d3d9..764f8797 100644 --- a/test/test_format_installer.sh +++ b/test/test_format_installer.sh @@ -8,6 +8,7 @@ rm -rf "${CONFIG_DIR}/.lnav/formats" HOME=${CONFIG_DIR} unset XDG_CONFIG_HOME export HOME +export YES_COLOR=1 ${lnav_test} -i ${srcdir}/formats/jsontest/format.json @@ -16,6 +17,19 @@ if ! test -f ${CONFIG_DIR}/.lnav/formats/installed/test_log.json; then exit 1 fi +run_cap_test ${lnav_test} -i ${srcdir}/formats/jsontest/format.json + +echo corrupt > ${CONFIG_DIR}/.lnav/formats/installed/test_log.json + +run_cap_test env TEST_COMMENT='overwrite file' ${lnav_test} -i ${srcdir}/formats/jsontest/format.json + +if ! test -f ${CONFIG_DIR}/.lnav/formats/installed/test_log.json.bak; then + echo "Format not backed up correctly?" + exit 1 +fi + +run_cap_test ${lnav_test} -i /non-existent/file + if test x"${TEST_GIT_INSTALL}" = x""; then # Hitting the git repos frequently is slow/noisy exit 0