summaryrefslogtreecommitdiff
path: root/apt-private
diff options
context:
space:
mode:
authorJulian Andres Klode <jak@debian.org>2024-12-19 13:12:35 +0000
committerJulian Andres Klode <jak@debian.org>2024-12-19 13:12:35 +0000
commitb1cb2d37d015579d208bb79f6c4977e20fde12a5 (patch)
treeb57e0ac1ce86a0e1fec730b0a3457bd48feec237 /apt-private
parentf54a561bc81d1bcbce6a107ad535f0c6d6e88d38 (diff)
parent6e260e26e4cd671f781151e24d89a17b29a55530 (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.cc1
-rw-r--r--apt-private/private-list.cc4
-rw-r--r--apt-private/private-main.cc5
-rw-r--r--apt-private/private-output.cc168
-rw-r--r--apt-private/private-output.h2
-rw-r--r--apt-private/private-search.cc3
-rw-r--r--apt-private/private-show.cc10
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)
{