diff --git a/qmltermwidget/lib/TerminalSession.cpp b/qmltermwidget/lib/TerminalSession.cpp index 3ade2e4..33b8bdb 100644 --- a/qmltermwidget/lib/TerminalSession.cpp +++ b/qmltermwidget/lib/TerminalSession.cpp @@ -325,6 +325,17 @@ void TerminalSession::setUserTitle(int what, const std::string& caption) updateTitle(); } +// The cwd link, which only a process's own user can read. +static std::optional workingDirectory(int pid) +{ + char path[PATH_MAX + 1]; + const ssize_t length = readlink(("/proc/" + std::to_string(pid) + "/cwd").c_str(), path, PATH_MAX); + if (length < 0) + return std::nullopt; + path[length] = '\0'; + return strings::sanitizedUtf8(path); +} + void TerminalSession::updateForegroundProcessInfo() { const int foregroundPid = _shellProcess->foregroundProcessGroup(); @@ -370,11 +381,8 @@ void TerminalSession::updateForegroundProcessInfo() return; _foregroundValid = true; - char path[PATH_MAX + 1]; - const ssize_t length = readlink((base + "/cwd").c_str(), path, PATH_MAX); - if (length >= 0) { - path[length] = '\0'; - _foregroundDir = strings::sanitizedUtf8(path); + if (std::optional dir = workingDirectory(foregroundPid)) { + _foregroundDir = *dir; _foregroundHasDir = true; } } @@ -388,7 +396,13 @@ std::string TerminalSession::foregroundProcessName() std::string TerminalSession::currentDir() { updateForegroundProcessInfo(); - return _foregroundValid && _foregroundHasDir ? _foregroundDir : std::string(); + if (_foregroundValid && _foregroundHasDir) + return _foregroundDir; + // A program in front that runs as another user, such as su, hides its + // directory; the shell's stands in for it. + if (processId() > 0 && processId() != _foregroundPid) + return workingDirectory(processId()).value_or(std::string()); + return std::string(); } bool TerminalSession::hasActiveProcess() const diff --git a/tests/session_test.cpp b/tests/session_test.cpp index 7f450ab..e23d061 100644 --- a/tests/session_test.cpp +++ b/tests/session_test.cpp @@ -12,6 +12,7 @@ #include "base/event_loop.h" #include "base/file.h" +#include "base/paths.h" #include "base/timer.h" #include "TerminalSession.h" @@ -112,3 +113,29 @@ TEST(SessionTest, ChildSeesTheTerminalEnvironment) ASSERT_TRUE(contents); EXPECT_TRUE(contents->starts_with("xterm-256color|truecolor|15;0|/dev/pts/")) << *contents; } + +TEST(SessionTest, CurrentDirBehindAProgramOfAnotherUser) +{ + set_kb_layout_dir(KB_LAYOUTS_DIR); + // Set-group-ID, so its working directory is not ours to read. + const std::string agent = cutefish::base::paths::findExecutable("ssh-agent"); + if (agent.empty()) + GTEST_SKIP() << "no ssh-agent"; + cutefish::base::EventLoop loop; + cutefish::base::file::TemporaryDirectory directory("/tmp", "session-cwd"); + ASSERT_TRUE(directory.isValid()); + + TerminalSession session; + session.setShellProgram("/bin/sh"); + session.setInitialWorkingDirectory(directory.path()); + session.startShellProgram(); + ASSERT_TRUE(waitFor(loop, [&] { return session.currentDir() == directory.path(); })); + + // Looked at only once the agent runs: its socket is there. + const std::string socket = directory.path() + "/agent"; + session.sendText(agent + " -D -a " + socket + "\r"); + ASSERT_TRUE(waitFor(loop, [&] { return std::filesystem::exists(socket); })); + ASSERT_EQ(session.foregroundProcessName(), "ssh-agent"); + EXPECT_EQ(session.currentDir(), directory.path()); + session.sendText("\x03"); +}