diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e068bb2..573b409b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 server’s + (https://codeberg.org/dnkl/foot/issues/1004). ### Changed diff --git a/client-protocol.h b/client-protocol.h index b0f39055..505825f6 100644 --- a/client-protocol.h +++ b/client-protocol.h @@ -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"); diff --git a/client.c b/client.c index a76d759a..7624e7db 100644 --- a/client.c +++ b/client.c @@ -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) diff --git a/completions/bash/footclient b/completions/bash/footclient index 9f752641..b672c247 100644 --- a/completions/bash/footclient +++ b/completions/bash/footclient @@ -14,6 +14,7 @@ _footclient() "--log-colorize" "--maximized" "--override" + "--client-environment" "--server-socket" "--term" "--title" diff --git a/completions/fish/footclient.fish b/completions/fish/footclient.fish index 8133f663..df3e1273 100644 --- a/completions/fish/footclient.fish +++ b/completions/fish/footclient.fish @@ -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" diff --git a/completions/zsh/_footclient b/completions/zsh/_footclient index 81b2ea95..b36644c6 100644 --- a/completions/zsh/_footclient +++ b/completions/zsh/_footclient @@ -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]' \ diff --git a/doc/footclient.1.scd b/doc/footclient.1.scd index 9cb4ef69..285ff184 100644 --- a/doc/footclient.1.scd +++ b/doc/footclient.1.scd @@ -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_. diff --git a/main.c b/main.c index 4011dcde..6da82ef2 100644 --- a/main.c +++ b/main.c @@ -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; } diff --git a/server.c b/server.c index 2c41b479..31b8c633 100644 --- a/server.c +++ b/server.c @@ -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; diff --git a/slave.c b/slave.c index 4fbd5c9b..db60d7b5 100644 --- a/slave.c +++ b/slave.c @@ -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; diff --git a/slave.h b/slave.h index 538588b1..4b47bfde 100644 --- a/slave.h +++ b/slave.h @@ -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); diff --git a/terminal.c b/terminal.c index 84edec65..b78279e4 100644 --- a/terminal.c +++ b/terminal.c @@ -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) { diff --git a/terminal.h b/terminal.h index 03297678..5b7d6d07 100644 --- a/terminal.h +++ b/terminal.h @@ -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);