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..ad88fe5b 100644 --- a/client.c +++ b/client.c @@ -25,11 +25,11 @@ #include "version.h" #include "xmalloc.h" -struct override { +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 +91,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 +103,29 @@ 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); + } +} + int main(int argc, char *const *argv) { @@ -140,6 +150,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 +163,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 +181,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 +232,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 +245,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 +273,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 +390,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 +410,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 +435,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; @@ -435,7 +462,7 @@ main(int argc, char *const *argv) /* Send overrides */ tll_foreach(overrides, it) { - const struct override *o = &it->item; + const struct string *o = &it->item; struct client_string s = {o->len}; if (sendall(fd, &s, sizeof(s)) < 0 || sendall(fd, o->str, o->len) < 0) @@ -455,6 +482,18 @@ main(int argc, char *const *argv) } } + /* Send environment */ + tll_foreach(envp, it) { + const struct string *e = &it->item; + struct client_string s = {e->len}; + if (sendall(fd, &s, sizeof(s)) < 0 || + sendall(fd, e->str, e->len) < 0) + { + LOG_ERRNO("failed to send setup packet (envp) to server"); + 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 +512,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/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..4e8610ce 100644 --- a/slave.c +++ b/slave.c @@ -145,8 +145,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 +232,7 @@ slave_exec(int ptmx, char *argv[], int err_fd, bool login_shell, } else file = argv[0]; - execvp(file, argv); + execvpe(file, argv, envp); err: (void)!write(err_fd, &errno, sizeof(errno)); @@ -246,8 +246,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 +319,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);