Merge branch 'footclient-environ'

Closes #1004
This commit is contained in:
Daniel Eklöf 2022-04-12 15:08:00 +02:00
commit bdef28c6d1
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
13 changed files with 177 additions and 48 deletions

View file

@ -49,6 +49,10 @@
allows setting/saving/restoring/querying the keypad mode.
* Sixel support can be disabled by setting `[tweak].sixel=no`
(https://codeberg.org/dnkl/foot/issues/950).
* footclient: `-E,--client-environment` command line option. When
used, the child process in the new terminal instance inherits the
environment from the footclient process instead of the servers
(https://codeberg.org/dnkl/foot/issues/1004).
### Changed

View file

@ -19,11 +19,13 @@ struct client_data {
uint16_t cwd_len;
uint16_t override_count;
uint16_t argc;
uint16_t env_count;
/* char cwd[static cwd_len]; */
/* char token[static token_len]; */
/* struct client_string overrides[static override_count]; */
/* struct client_string argv[static argc]; */
/* struct client_string envp[static env_count]; */
} __attribute__((packed));
_Static_assert(sizeof(struct client_data) == 8, "protocol struct size error");
_Static_assert(sizeof(struct client_data) == 10, "protocol struct size error");

105
client.c
View file

@ -25,11 +25,13 @@
#include "version.h"
#include "xmalloc.h"
struct override {
extern char **environ;
struct string {
size_t len;
char *str;
};
typedef tll(struct override) override_list_t;
typedef tll(struct string) string_list_t;
static volatile sig_atomic_t aborted = 0;
@ -91,6 +93,7 @@ print_usage(const char *prog_name)
" -H,--hold remain open after child process exits\n"
" -N,--no-wait detach the client process from the running terminal, exiting immediately\n"
" -o,--override=[section.]key=value override configuration option\n"
" -E, --client-environment exec shell using footclient's environment, instead of the server's\n"
" -d,--log-level={info|warning|error|none} log level (info)\n"
" -l,--log-colorize=[{never|always|auto}] enable/disable colorization of log output on stderr\n"
" -v,--version show the version number and quit\n"
@ -102,20 +105,45 @@ print_usage(const char *prog_name)
}
static bool NOINLINE
push_override(override_list_t *overrides, const char *s, uint64_t *total_len)
push_string(string_list_t *string_list, const char *s, uint64_t *total_len)
{
size_t len = strlen(s) + 1;
if (len >= 1 << (8 * sizeof(struct client_string))) {
LOG_ERR("override length overflow");
if (len >= 1 << (8 * sizeof(uint16_t))) {
LOG_ERR("string length overflow");
return false;
}
struct override o = {len, xstrdup(s)};
tll_push_back(*overrides, o);
struct string o = {len, xstrdup(s)};
tll_push_back(*string_list, o);
*total_len += sizeof(struct client_string) + o.len;
return true;
}
static void
free_string_list(string_list_t *string_list)
{
tll_foreach(*string_list, it) {
free(it->item.str);
tll_remove(*string_list, it);
}
}
static bool
send_string_list(int fd, const string_list_t *string_list)
{
tll_foreach(*string_list, it) {
const struct client_string s = {it->item.len};
if (sendall(fd, &s, sizeof(s)) < 0 ||
sendall(fd, it->item.str, s.len) < 0)
{
LOG_ERRNO("failed to send setup packet to server");
return false;
}
}
return true;
}
int
main(int argc, char *const *argv)
{
@ -140,6 +168,7 @@ main(int argc, char *const *argv)
{"hold", no_argument, NULL, 'H'},
{"no-wait", no_argument, NULL, 'N'},
{"override", required_argument, NULL, 'o'},
{"client-environment", no_argument, NULL, 'E'},
{"log-level", required_argument, NULL, 'd'},
{"log-colorize", optional_argument, NULL, 'l'},
{"version", no_argument, NULL, 'v'},
@ -152,6 +181,7 @@ main(int argc, char *const *argv)
enum log_class log_level = LOG_CLASS_INFO;
enum log_colorize log_colorize = LOG_COLORIZE_AUTO;
bool hold = false;
bool client_environment = false;
/* Used to format overrides */
bool no_wait = false;
@ -169,35 +199,36 @@ main(int argc, char *const *argv)
/* malloc:ed and needs to be in scope of all goto's */
int fd = -1;
char *_cwd = NULL;
override_list_t overrides = tll_init();
struct client_string *cargv = NULL;
string_list_t overrides = tll_init();
string_list_t envp = tll_init();
while (true) {
int c = getopt_long(argc, argv, "+t:T:a:w:W:mFLD:s:HNo:d:l::veh", longopts, NULL);
int c = getopt_long(argc, argv, "+t:T:a:w:W:mFLD:s:HNo:Ed:l::veh", longopts, NULL);
if (c == -1)
break;
switch (c) {
case 't':
snprintf(buf, sizeof(buf), "term=%s", optarg);
if (!push_override(&overrides, buf, &total_len))
if (!push_string(&overrides, buf, &total_len))
goto err;
break;
case 'T':
snprintf(buf, sizeof(buf), "title=%s", optarg);
if (!push_override(&overrides, buf, &total_len))
if (!push_string(&overrides, buf, &total_len))
goto err;
break;
case 'a':
snprintf(buf, sizeof(buf), "app-id=%s", optarg);
if (!push_override(&overrides, buf, &total_len))
if (!push_string(&overrides, buf, &total_len))
goto err;
break;
case 'L':
if (!push_override(&overrides, "login-shell=yes", &total_len))
if (!push_string(&overrides, "login-shell=yes", &total_len))
goto err;
break;
@ -219,7 +250,7 @@ main(int argc, char *const *argv)
}
snprintf(buf, sizeof(buf), "initial-window-size-pixels=%ux%u", width, height);
if (!push_override(&overrides, buf, &total_len))
if (!push_string(&overrides, buf, &total_len))
goto err;
break;
}
@ -232,18 +263,18 @@ main(int argc, char *const *argv)
}
snprintf(buf, sizeof(buf), "initial-window-size-chars=%ux%u", width, height);
if (!push_override(&overrides, buf, &total_len))
if (!push_string(&overrides, buf, &total_len))
goto err;
break;
}
case 'm':
if (!push_override(&overrides, "initial-window-mode=maximized", &total_len))
if (!push_string(&overrides, "initial-window-mode=maximized", &total_len))
goto err;
break;
case 'F':
if (!push_override(&overrides, "initial-window-mode=fullscreen", &total_len))
if (!push_string(&overrides, "initial-window-mode=fullscreen", &total_len))
goto err;
break;
@ -260,10 +291,14 @@ main(int argc, char *const *argv)
break;
case 'o':
if (!push_override(&overrides, optarg, &total_len))
if (!push_string(&overrides, optarg, &total_len))
goto err;
break;
case 'E':
client_environment = true;
break;
case 'd': {
int lvl = log_level_from_string(optarg);
if (unlikely(lvl < 0)) {
@ -373,9 +408,17 @@ main(int argc, char *const *argv)
cwd = _cwd;
}
if (client_environment) {
for (char **e = environ; *e != NULL; e++) {
if (!push_string(&envp, *e, &total_len))
goto err;
}
}
/* String lengths, including NULL terminator */
const size_t cwd_len = strlen(cwd) + 1;
const size_t override_count = tll_length(overrides);
const size_t env_count = tll_length(envp);
const struct client_data data = {
.hold = hold,
@ -385,6 +428,7 @@ main(int argc, char *const *argv)
.cwd_len = cwd_len,
.override_count = override_count,
.argc = argc,
.env_count = env_count,
};
/* Total packet length, not (yet) including argv[] */
@ -409,7 +453,8 @@ main(int argc, char *const *argv)
cwd_len >= 1 << (8 * sizeof(data.cwd_len)) ||
token_len >= 1 << (8 * sizeof(data.token_len)) ||
override_count > (size_t)(unsigned int)data.override_count ||
argc > (int)(unsigned int)data.argc)
argc > (int)(unsigned int)data.argc ||
env_count > (size_t)(unsigned int)data.env_count)
{
LOG_ERR("size overflow");
goto err;
@ -434,16 +479,8 @@ main(int argc, char *const *argv)
}
/* Send overrides */
tll_foreach(overrides, it) {
const struct override *o = &it->item;
struct client_string s = {o->len};
if (sendall(fd, &s, sizeof(s)) < 0 ||
sendall(fd, o->str, o->len) < 0)
{
LOG_ERRNO("failed to send setup packet (overrides) to server");
goto err;
}
}
if (!send_string_list(fd, &overrides))
goto err;
/* Send argv[] */
for (size_t i = 0; i < argc; i++) {
@ -455,6 +492,10 @@ main(int argc, char *const *argv)
}
}
/* Send environment */
if (!send_string_list(fd, &envp))
goto err;
struct sigaction sa = {.sa_handler = &sig_handler};
sigemptyset(&sa.sa_mask);
if (sigaction(SIGINT, &sa, NULL) < 0 || sigaction(SIGTERM, &sa, NULL) < 0) {
@ -473,10 +514,8 @@ main(int argc, char *const *argv)
ret = exit_code;
err:
tll_foreach(overrides, it) {
free(it->item.str);
tll_remove(overrides, it);
}
free_string_list(&envp);
free_string_list(&overrides);
free(cargv);
free(_cwd);
if (fd != -1)

View file

@ -14,6 +14,7 @@ _footclient()
"--log-colorize"
"--maximized"
"--override"
"--client-environment"
"--server-socket"
"--term"
"--title"

View file

@ -12,6 +12,7 @@ complete -c footclient -F -s s -l server-socket
complete -c footclient -s H -l hold -d "remain open after child process exits"
complete -c footclient -s N -l no-wait -d "detach the client process from the running terminal, exiting immediately"
complete -c footclient -x -s o -l override -d "configuration option to override, in form SECTION.KEY=VALUE"
complete -c footclient -s E -l client-environment -d "child process inherits footclient's environment, instead of the server's"
complete -c footclient -x -s d -l log-level -a "info warning error none" -d "log-level (info)"
complete -c footclient -x -s l -l log-colorize -a "always never auto" -d "enable or disable colorization of log output on stderr"
complete -c footclient -s v -l version -d "show the version number and quit"

View file

@ -15,6 +15,7 @@ _arguments \
'(-H --hold)'{-H,--hold}'[remain open after child process exits]' \
'(-N --no-wait)'{-N,--no-wait}'[detach the client process from the running terminal, exiting immediately]' \
'(-o --override)'{-o,--override}'[configuration option to override, in form SECTION.KEY=VALUE]:()' \
'(-E --client-environment)'{-E,--client-environment}"[child process inherits footclient's environment, instead of the server's]" \
'(-d --log-level)'{-d,--log-level}'[log level (info)]:loglevel:(info warning error none)' \
'(-l --log-colorize)'{-l,--log-colorize}'[enable or disable colorization of log output on stderr]:logcolor:(never always auto)' \
'(-v --version)'{-v,--version}'[show the version number and quit]' \

View file

@ -69,6 +69,10 @@ terminal has terminated.
Override an option set in the configuration file. If _SECTION_ is not
given, defaults to _main_.
*-E*,*--client-environment*
The child process in the new terminal instance will use
footclient's environment, instead of the server's.
*-d*,*--log-level*={*info*,*warning*,*error*,*none*}
Log level, used both for log output on stderr as well as
syslog. Default: _info_.

2
main.c
View file

@ -608,7 +608,7 @@ main(int argc, char *const *argv)
if (!as_server && (term = term_init(
&conf, fdm, reaper, wayl, "foot", cwd, token,
argc, argv,
argc, argv, NULL,
&term_shutdown_cb, &shutdown_ctx)) == NULL) {
goto out;
}

View file

@ -146,6 +146,7 @@ fdm_client(struct fdm *fdm, int fd, int events, void *data)
char **argv = NULL;
config_override_t overrides = tll_init();
char **envp = NULL;
if (events & EPOLLHUP)
goto shutdown;
@ -281,7 +282,21 @@ fdm_client(struct fdm *fdm, int fd, int events, void *data)
argv[i] = (char *)p; p += arg.len;
LOG_DBG("argv[%hu] = %.*s", i, arg.len, argv[i]);
}
argv[cdata.argc] = NULL;
/* envp */
envp = cdata.env_count != 0
? xcalloc(cdata.env_count + 1, sizeof(envp[0]))
: NULL;
for (uint16_t i = 0; i < cdata.env_count; i++) {
struct client_string e;
CHECK_BUF(sizeof(e));
memcpy(&e, p, sizeof(e)); p += sizeof(e);
CHECK_BUF_AND_NULL(e.len);
envp[i] = (char *)p; p += e.len;
LOG_DBG("env[%hu] = %.*s", i, e.len, envp[i]);
}
#undef CHECK_BUF_AND_NULL
#undef CHECK_BUF
@ -317,7 +332,7 @@ fdm_client(struct fdm *fdm, int fd, int events, void *data)
instance->terminal = term_init(
conf != NULL ? conf : server->conf,
server->fdm, server->reaper, server->wayl, "footclient", cwd, token,
cdata.argc, argv, &term_shutdown_handler, instance);
cdata.argc, argv, envp, &term_shutdown_handler, instance);
if (instance->terminal == NULL) {
LOG_ERR("failed to instantiate new terminal");
@ -336,6 +351,7 @@ fdm_client(struct fdm *fdm, int fd, int events, void *data)
instance->client = client;
client->instance = instance;
free(argv);
free(envp);
tll_free_and_free(overrides, free);
}
@ -345,6 +361,7 @@ shutdown:
LOG_DBG("client FD=%d: disconnected", client->fd);
free(argv);
free(envp);
tll_free_and_free(overrides, free);
fdm_del(fdm, fd);
client->fd = -1;

72
slave.c
View file

@ -23,6 +23,65 @@
#include "tokenize.h"
#include "xmalloc.h"
extern char **environ;
#if defined(__FreeBSD__)
static char *
find_file_in_path(const char *file)
{
if (strchr(file, '/') != NULL)
return xstrdup(file);
const char *env_path = getenv("PATH");
char *path_list = NULL;
if (env_path != NULL && env_path[0] != '\0')
path_list = xstrdup(env_path);
else {
size_t sc_path_len = confstr(_CS_PATH, NULL, 0);
if (sc_path_len > 0) {
path_list = xmalloc(sc_path_len);
confstr(_CS_PATH, path_list, sc_path_len);
} else
return xstrdup(file);
}
for (const char *path = strtok(path_list, ":");
path != NULL;
path = strtok(NULL, ":"))
{
char *full = xasprintf("%s/%s", path, file);
if (access(full, F_OK) == 0) {
free(path_list);
return full;
}
free(full);
}
free(path_list);
return xstrdup(file);
}
static int
foot_execvpe(const char *file, char *const argv[], char *const envp[])
{
char *path = find_file_in_path(file);
int ret = execve(path, argv, envp);
/*
* Getting here is an error
*/
free(path);
return ret;
}
#else /* !__FreeBSD__ */
#define foot_execvpe(file, argv, envp) execvpe(file, argv, envp)
#endif /* !__FreeBSD__ */
static bool
is_valid_shell(const char *shell)
{
@ -145,8 +204,8 @@ emit_notifications(int fd, const user_notifications_t *notifications)
}
static noreturn void
slave_exec(int ptmx, char *argv[], int err_fd, bool login_shell,
const user_notifications_t *notifications)
slave_exec(int ptmx, char *argv[], char *const envp[], int err_fd,
bool login_shell, const user_notifications_t *notifications)
{
int pts = -1;
const char *pts_name = ptsname(ptmx);
@ -232,7 +291,7 @@ slave_exec(int ptmx, char *argv[], int err_fd, bool login_shell,
} else
file = argv[0];
execvp(file, argv);
foot_execvpe(file, argv, envp);
err:
(void)!write(err_fd, &errno, sizeof(errno));
@ -246,8 +305,8 @@ err:
pid_t
slave_spawn(int ptmx, int argc, const char *cwd, char *const *argv,
const char *term_env, const char *conf_shell, bool login_shell,
const user_notifications_t *notifications)
char *const *envp, const char *term_env, const char *conf_shell,
bool login_shell, const user_notifications_t *notifications)
{
int fork_pipe[2];
if (pipe2(fork_pipe, O_CLOEXEC) < 0) {
@ -319,7 +378,8 @@ slave_spawn(int ptmx, int argc, const char *cwd, char *const *argv,
if (is_valid_shell(shell_argv[0]))
setenv("SHELL", shell_argv[0], 1);
slave_exec(ptmx, shell_argv, fork_pipe[1], login_shell, notifications);
slave_exec(ptmx, shell_argv, envp != NULL ? envp : environ,
fork_pipe[1], login_shell, notifications);
BUG("Unexpected return from slave_exec()");
break;

View file

@ -6,6 +6,6 @@
#include "user-notification.h"
pid_t slave_spawn(
int ptmx, int argc, const char *cwd, char *const *argv, const char *term_env,
const char *conf_shell, bool login_shell,
int ptmx, int argc, const char *cwd, char *const *argv, char *const *envp,
const char *term_env, const char *conf_shell, bool login_shell,
const user_notifications_t *notifications);

View file

@ -1034,7 +1034,7 @@ static void fdm_client_terminated(
struct terminal *
term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
struct wayland *wayl, const char *foot_exe, const char *cwd,
const char *token, int argc, char *const *argv,
const char *token, int argc, char *const *argv, char *const *envp,
void (*shutdown_cb)(void *data, int exit_code), void *shutdown_data)
{
int ptmx = -1;
@ -1241,7 +1241,7 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
/* Start the slave/client */
if ((term->slave = slave_spawn(
term->ptmx, argc, term->cwd, argv,
term->ptmx, argc, term->cwd, argv, envp,
conf->term, conf->shell, conf->login_shell,
&conf->notifications)) == -1)
{

View file

@ -670,7 +670,7 @@ struct config;
struct terminal *term_init(
const struct config *conf, struct fdm *fdm, struct reaper *reaper,
struct wayland *wayl, const char *foot_exe, const char *cwd,
const char *token, int argc, char *const *argv,
const char *token, int argc, char *const *argv, char *const *envp,
void (*shutdown_cb)(void *data, int exit_code), void *shutdown_data);
bool term_shutdown(struct terminal *term);