Skip to content

Commit c112979

Browse files
committed
util: extract preferred_shell helper used by cmd and terminal
Both extensions had their own copy of the same shell-detection loop checking /bin/{zsh,bash,sh,ash}. Moved into util/shell.hpp as preferred_shell()/preferred_shell_name(), cached once per process. ensure_home_env() lives in the same header so other extensions can also rely on $HOME being populated before forking a child.
1 parent d6c70e4 commit c112979

3 files changed

Lines changed: 65 additions & 42 deletions

File tree

src/thinger/iotmp/extensions/cmd/cmd.cpp

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,15 @@
11
#include "cmd.hpp"
2+
#include "../../util/shell.hpp"
23
#include <boost/asio/io_context.hpp>
34
#include <boost/asio/buffer.hpp>
45
#include <boost/asio/steady_timer.hpp>
56
#include <boost/asio/readable_pipe.hpp>
67
#include <boost/asio/writable_pipe.hpp>
78
#include <boost/process/v2/process.hpp>
89
#include <boost/process/v2/stdio.hpp>
9-
#include <cstdlib>
10-
#include <filesystem>
11-
#include <pwd.h>
12-
#include <unistd.h>
1310

1411
namespace thinger::iotmp{
1512

16-
namespace {
17-
const std::string& preferred_shell() {
18-
static const std::string shell = [] {
19-
// Preference order matches the interactive terminal extension.
20-
for (const char* name : {"zsh", "bash", "sh", "ash"}) {
21-
std::string path = std::string("/bin/") + name;
22-
if (std::filesystem::exists(path)) return path;
23-
}
24-
return std::string("/bin/sh");
25-
}();
26-
return shell;
27-
}
28-
29-
// Child processes of the agent inherit its environment. systemd units
30-
// without User= or in older systemd do not populate HOME, which
31-
// surprises scripts that rely on it (cd ~, tilde expansion, etc.).
32-
// Resolve HOME from the password database on first use.
33-
void ensure_home_env() {
34-
static const bool done = [] {
35-
if (std::getenv("HOME")) return true;
36-
if (passwd* pw = getpwuid(getuid()); pw && pw->pw_dir && *pw->pw_dir) {
37-
setenv("HOME", pw->pw_dir, 0);
38-
}
39-
return true;
40-
}();
41-
(void)done;
42-
}
43-
}
44-
4513
cmd::cmd(client& client)
4614
{
4715
ensure_home_env();

src/thinger/iotmp/extensions/terminal/terminal_session.cpp

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
#include "terminal_session.hpp"
2+
#include "../../util/shell.hpp"
23

34
#include <boost/process.hpp>
45
#include <boost/asio.hpp>
5-
#include <filesystem>
66
#include <thinger/util/logger.hpp>
77

88
#ifdef __linux__
@@ -18,14 +18,7 @@ terminal_session::terminal_session(client& client, uint16_t stream_id, std::stri
1818
: stream_session(client, stream_id, std::move(session)),
1919
descriptor_(client.get_io_context())
2020
{
21-
// Find available terminal
22-
std::vector<std::string> terminals{"zsh", "bash", "sh", "ash"};
23-
for(auto& terminal : terminals) {
24-
if(std::filesystem::exists("/bin/" + terminal)) {
25-
terminal_ = terminal;
26-
break;
27-
}
28-
}
21+
terminal_ = preferred_shell_name();
2922

3023
// Initialize cols and rows from parameters
3124
cols_ = get_value(parameters, "cols", 80u);

src/thinger/iotmp/util/shell.hpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#ifndef THINGER_IOTMP_UTIL_SHELL_HPP
2+
#define THINGER_IOTMP_UTIL_SHELL_HPP
3+
4+
#include <cstdlib>
5+
#include <filesystem>
6+
#include <string>
7+
8+
#ifndef _WIN32
9+
#include <pwd.h>
10+
#include <unistd.h>
11+
#endif
12+
13+
namespace thinger::iotmp {
14+
15+
// Returns the absolute path of the shell the agent should use for both
16+
// non-interactive command execution (cmd extension) and interactive
17+
// terminal sessions. Preference order: zsh, bash, sh, ash. Resolved once
18+
// per process and cached.
19+
inline const std::string& preferred_shell() {
20+
static const std::string shell = [] {
21+
for (const char* name : {"zsh", "bash", "sh", "ash"}) {
22+
std::string path = std::string("/bin/") + name;
23+
if (std::filesystem::exists(path)) return path;
24+
}
25+
return std::string("/bin/sh");
26+
}();
27+
return shell;
28+
}
29+
30+
// Returns the name of the preferred shell without its directory (e.g.,
31+
// "bash"). Useful for interactive terminal sessions that execve with a
32+
// plain basename.
33+
inline const std::string& preferred_shell_name() {
34+
static const std::string name = [] {
35+
const std::string& full = preferred_shell();
36+
auto slash = full.find_last_of('/');
37+
return slash == std::string::npos ? full : full.substr(slash + 1);
38+
}();
39+
return name;
40+
}
41+
42+
// Populates $HOME in the current process environment if it is unset.
43+
// Child processes launched by the agent inherit this, so scripts that
44+
// rely on HOME (cd ~, tilde expansion) keep working even when the
45+
// service was started without User= or on older systemd that does not
46+
// populate HOME. Idempotent and safe to call multiple times.
47+
inline void ensure_home_env() {
48+
#ifndef _WIN32
49+
static const bool done = [] {
50+
if (std::getenv("HOME")) return true;
51+
if (passwd* pw = getpwuid(getuid()); pw && pw->pw_dir && *pw->pw_dir) {
52+
setenv("HOME", pw->pw_dir, 0);
53+
}
54+
return true;
55+
}();
56+
(void)done;
57+
#endif
58+
}
59+
60+
} // namespace thinger::iotmp
61+
62+
#endif // THINGER_IOTMP_UTIL_SHELL_HPP

0 commit comments

Comments
 (0)