diff options
| author | Julian Andres Klode <jak@debian.org> | 2024-12-19 13:12:35 +0000 |
|---|---|---|
| committer | Julian Andres Klode <jak@debian.org> | 2024-12-19 13:12:35 +0000 |
| commit | b1cb2d37d015579d208bb79f6c4977e20fde12a5 (patch) | |
| tree | b57e0ac1ce86a0e1fec730b0a3457bd48feec237 /apt-private | |
| parent | f54a561bc81d1bcbce6a107ad535f0c6d6e88d38 (diff) | |
| parent | 6e260e26e4cd671f781151e24d89a17b29a55530 (diff) | |
Merge branch 'pager' into 'main'
Introduce automatic pager for read commands
See merge request apt-team/apt!410
Diffstat (limited to 'apt-private')
| -rw-r--r-- | apt-private/private-cmndline.cc | 1 | ||||
| -rw-r--r-- | apt-private/private-list.cc | 4 | ||||
| -rw-r--r-- | apt-private/private-main.cc | 5 | ||||
| -rw-r--r-- | apt-private/private-output.cc | 168 | ||||
| -rw-r--r-- | apt-private/private-output.h | 2 | ||||
| -rw-r--r-- | apt-private/private-search.cc | 3 | ||||
| -rw-r--r-- | apt-private/private-show.cc | 10 |
7 files changed, 189 insertions, 4 deletions
diff --git a/apt-private/private-cmndline.cc b/apt-private/private-cmndline.cc index 5d4eeb5cb..cbf787aa9 100644 --- a/apt-private/private-cmndline.cc +++ b/apt-private/private-cmndline.cc @@ -509,6 +509,7 @@ static void BinarySpecificConfiguration(char const * const Binary) /*{{{*/ _config->CndSet("Binary::apt::APT::Keep-Downloaded-Packages", false); _config->CndSet("Binary::apt::APT::Get::Update::InteractiveReleaseInfoChanges", true); _config->CndSet("Binary::apt::APT::Cmd::Pattern-Only", true); + _config->CndSet("Binary::apt::Pager", true); if (isatty(STDIN_FILENO)) _config->CndSet("Binary::apt::Dpkg::Lock::Timeout", -1); diff --git a/apt-private/private-list.cc b/apt-private/private-list.cc index eee657c46..6536c8df7 100644 --- a/apt-private/private-list.cc +++ b/apt-private/private-list.cc @@ -131,6 +131,10 @@ bool DoList(CommandLine &Cmd) GetLocalitySortedVersionSet(CacheFile, &bag, matcher, &progress); bool const ShowAllVersions = _config->FindB("APT::Cmd::All-Versions", false); std::map<std::string, std::string> output_map; + + if (not InitOutputPager()) + return false; + for (LocalitySortedVersionSet::iterator V = bag.begin(); V != bag.end(); ++V) { std::stringstream outs; diff --git a/apt-private/private-main.cc b/apt-private/private-main.cc index 39ad07b36..7b4f05a50 100644 --- a/apt-private/private-main.cc +++ b/apt-private/private-main.cc @@ -6,6 +6,7 @@ #include <apt-pkg/strutl.h> #include <apt-private/private-main.h> +#include <apt-private/private-output.h> #include <iostream> #include <locale> @@ -78,8 +79,8 @@ void CheckIfCalledByScript(int argc, const char *argv[]) /*{{{*/ { if (unlikely(argc < 1)) return; - if(!isatty(STDOUT_FILENO) && - _config->FindB("Apt::Cmd::Disable-Script-Warning", false) == false) + if (not IsStdoutAtty() && + _config->FindB("Apt::Cmd::Disable-Script-Warning", false) == false) { // NOTE: CLI interface is redundant on the I/interface, this is // intentional to make it easier to read. diff --git a/apt-private/private-output.cc b/apt-private/private-output.cc index 79a97128c..e3f795cf3 100644 --- a/apt-private/private-output.cc +++ b/apt-private/private-output.cc @@ -6,6 +6,7 @@ #include <apt-pkg/configuration.h> #include <apt-pkg/depcache.h> #include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> #include <apt-pkg/pkgcache.h> #include <apt-pkg/pkgrecords.h> #include <apt-pkg/policy.h> @@ -18,11 +19,13 @@ #include <cstdio> #include <cstdlib> #include <cstring> +#include <fcntl.h> #include <iomanip> #include <iostream> #include <langinfo.h> #include <regex.h> #include <sys/ioctl.h> +#include <sys/wait.h> #include <termios.h> #include <unistd.h> @@ -41,6 +44,14 @@ std::ofstream devnull("/dev/null"); unsigned int ScreenWidth = 80 - 1; /* - 1 for the cursor */ +static pid_t Pager = -1; +static constexpr std::array<int, 5> PagerSignalsToHandle = { + SIGINT, + SIGHUP, + SIGTERM, + SIGQUIT, + SIGPIPE, +}; // SigWinch - Window size change signal handler /*{{{*/ // --------------------------------------------------------------------- @@ -56,9 +67,162 @@ static void SigWinch(int) #endif } /*}}}*/ + +bool IsStdoutAtty() +{ + static bool is = isatty(STDOUT_FILENO); + return is; +} + +static void WaitPager() +{ + if (Pager != -1) + { + c0out.flush(); + c1out.flush(); + c2out.flush(); + std::cerr.flush(); + std::cout.flush(); + fflush(stdout); + fflush(stderr); + close(STDOUT_FILENO); + close(STDERR_FILENO); + waitpid(Pager, nullptr, 0); + Pager = -1; + } +} + +static void SignalPager(int signo) +{ + if (Pager != -1) + { + // We can't flush from inside the signal handler. + close(STDOUT_FILENO); + close(STDERR_FILENO); + waitpid(Pager, nullptr, 0); + Pager = -1; + for (auto signalToHandle : PagerSignalsToHandle) + signal(signalToHandle, SIG_DFL); + raise(signo); + } +} + +static std::string DeterminePager() +{ + if (not IsStdoutAtty() || not _config->FindB("Pager", false)) + return ""; + if (auto pager = getenv("APT_PAGER"); pager) + return pager; + if (auto pager = getenv("PAGER"); pager) + return pager; + return DEFAULT_PAGER; +} + +bool InitOutputPager() +{ + // InitOutputPager() behaves like a singleton, do not run twice- + static bool alreadyRan = false; + if (alreadyRan) + return true; + alreadyRan = true; + + auto pager = DeterminePager(); + if (pager.empty() || pager == "cat") + return true; + + auto pagerEnv = VectorizeString(PAGER_ENV, '\n'); + + int pipefds[2] = {-1, -1}; + int notifyPipe[2] = {-1, -1}; + + DEFER([&] () { + close(pipefds[0]); + close(pipefds[1]); + close(notifyPipe[0]); + close(notifyPipe[1]); + }); + + if (pipe(pipefds) || pipe(notifyPipe)) + return _error->Errno("pipe", "Failed to setup pipe"); + + Pager = ExecFork(); + if (Pager < 0) + return _error->Errno("fork", "Failed to fork"); + if (Pager == 0) + { + // Child process + if (dup2(pipefds[0], STDIN_FILENO) == -1) + goto err; + if (dup2(STDOUT_FILENO, STDERR_FILENO) == -1) + goto err; + + for (int sig = 1; sig < NSIG; sig++) + signal(sig, SIG_DFL); + + for (auto &v: pagerEnv) + putenv(v.data()); + + { + // If our pager name contains a space we need to invoke it in a shell. Boooo! + char *cmd[] = {(char*)"/bin/sh", (char*)"-c", pager.data(), nullptr}; + if (std::none_of(pager.begin(), pager.end(), isspace_ascii)) + { + cmd[0] = pager.data(); + cmd[1] = nullptr; + } + execvp(cmd[0], cmd); + } + err: + int err = errno; + FileFd::Write(notifyPipe[1], &err, sizeof(err)); + _exit(128); + } + + // Parent process. + + // Figure out if we were able to execvp() the child successfully. To do so, + // read from the notifyPipe. If execvp() was successful, it was closed due + // to CLOEXEC. On failure, our child code writes an errno to it. + _error->PushToStack(); + close(notifyPipe[1]); // Need to close our write end so our read doesn't block + notifyPipe[1] = -1; + if (int err = 0; FileFd::Read(notifyPipe[0], &err, sizeof(err))) { + errno = err; + _error->WarningE("PagerSetup", "Could not execute pager"); + _error->MergeWithStack(); + return true; + } + _error->RevertToStack(); + + // When running a pager, we must not be reading from stdin/tty, so + // let's be safe and open /dev/null in its place. + if (int const nullfd = open("/dev/null", O_RDONLY); nullfd != -1) + { + dup2(nullfd, STDIN_FILENO); + close(nullfd); + } + + // Redirect the output(s) to the pager + if (dup2(pipefds[1], STDOUT_FILENO) == -1) + abort(); + if (isatty(STDERR_FILENO) && dup2(pipefds[1], STDERR_FILENO) == -1) + abort(); + + // From now on, we can't show any progress messages as we are outputting + // to the pager. + _config->CndSet("quiet::NoUpdate", "1"); + + // Setup signal handlers and exit handlers for the pager, such that + // we wait for it. + for (auto signalToHandle : PagerSignalsToHandle) + signal(signalToHandle, SignalPager); + atexit(WaitPager); + + return true; +} bool InitOutput(std::basic_streambuf<char> * const out) /*{{{*/ { - if (!isatty(STDOUT_FILENO) && _config->FindI("quiet", -1) == -1) + if (not IsStdoutAtty() && _config->FindI("quiet", -1) == -1) _config->Set("quiet","1"); c0out.rdbuf(out); @@ -89,7 +253,7 @@ bool InitOutput(std::basic_streambuf<char> * const out) /*{{{*/ SigWinch(0); } - if (isatty(STDOUT_FILENO) == 0 || not _config->FindB("APT::Color", true) || getenv("NO_COLOR") != nullptr || getenv("APT_NO_COLOR") != nullptr) + if (not IsStdoutAtty() || not _config->FindB("APT::Color", true) || getenv("NO_COLOR") != nullptr || getenv("APT_NO_COLOR") != nullptr) { _config->Set("APT::Color", false); _config->Set("APT::Color::Highlight", ""); diff --git a/apt-private/private-output.h b/apt-private/private-output.h index 8e1d47225..aaa9a8ed9 100644 --- a/apt-private/private-output.h +++ b/apt-private/private-output.h @@ -26,6 +26,8 @@ APT_PUBLIC extern std::ofstream devnull; APT_PUBLIC extern unsigned int ScreenWidth; APT_PUBLIC bool InitOutput(std::basic_streambuf<char> * const out = std::cout.rdbuf()); +APT_PUBLIC bool InitOutputPager() APT_MUSTCHECK; +APT_PUBLIC bool IsStdoutAtty(); void ListSingleVersion(pkgCacheFile &CacheFile, pkgRecords &records, pkgCache::VerIterator const &V, std::ostream &out, diff --git a/apt-private/private-search.cc b/apt-private/private-search.cc index 19a3bf0fa..d6654eb5e 100644 --- a/apt-private/private-search.cc +++ b/apt-private/private-search.cc @@ -169,6 +169,9 @@ static bool FullTextSearch(CommandLine &CmdL) /*{{{*/ APT_FREE_PATTERNS(); progress.Done(); + if (not InitOutputPager()) + return false; + // FIXME: SORT! and make sorting flexible (alphabetic, by pkg status) // output the sorted map std::map<std::string, std::string>::const_iterator K; diff --git a/apt-private/private-show.cc b/apt-private/private-show.cc index ceef6707e..58c5d92c9 100644 --- a/apt-private/private-show.cc +++ b/apt-private/private-show.cc @@ -351,6 +351,10 @@ bool ShowPackage(CommandLine &CmdL) /*{{{*/ int const ShowVersion = _config->FindI("APT::Cache::Show::Version", 1); pkgRecords Recs(CacheFile); + + if (not InitOutputPager()) + return false; + for (APT::VersionList::const_iterator Ver = verset.begin(); Ver != verset.end(); ++Ver) { pkgCache::VerFileIterator Vf; @@ -441,6 +445,9 @@ bool ShowSrcPackage(CommandLine &CmdL) /*{{{*/ if (_error->PendingError() == true) return false; + if (not InitOutputPager()) + return false; + bool found = false; // avoid showing identical records std::set<std::string> seen; @@ -496,6 +503,9 @@ bool Policy(CommandLine &CmdL) if (unlikely(Plcy == nullptr)) return false; + if (not InitOutputPager()) + return false; + // Print out all of the package files if (CmdL.FileList[1] == 0) { |
