diff --git a/CHANGELOG.md b/CHANGELOG.md index d7f41bb5..01309223 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -82,9 +82,12 @@ `cursor-shape-v1` Wayland protocols ([#1573][1573]). * Crash in `--server` mode when one or more environment variables are set in `[environment]`. +* Environment variables normally set by foot lost with `footclient + -E,--client-environment` ([#1568][1568]). [1531]: https://codeberg.org/dnkl/foot/issues/1531 [1573]: https://codeberg.org/dnkl/foot/issues/1573 +[1568]: https://codeberg.org/dnkl/foot/issues/1568 ### Security diff --git a/doc/foot.1.scd b/doc/foot.1.scd index 3da6fd7e..385f9721 100644 --- a/doc/foot.1.scd +++ b/doc/foot.1.scd @@ -554,16 +554,31 @@ In all other cases, the exit code is that of the client application set according to either the *--term* command-line option or the *term* config option in *foot.ini*(5). -*PWD* - Current working directory (at the time of launching foot) - *COLORTERM* This variable is set to *truecolor*, to indicate to client applications that 24-bit RGB colors are supported. +*PWD* + Current working directory (at the time of launching foot) + +*SHELL* + Set to the launched shell, if the shell is valid (it is listed in + */etc/shells*). + In addition to the variables listed above, custom environment variables may be defined in *foot.ini*(5). +## Variables *unset* in the child process + +*TERM_PROGRAM* +*TERM_PROGRAM_VERSION* + These environment variables are set by certain other terminal + emulators. We unset them, to prevent applications from + misdetecting foot. + +In addition to the variables listed above, custom environment +variables to unset may be defined in *foot.ini*(5). + # BUGS Please report bugs to https://codeberg.org/dnkl/foot/issues diff --git a/doc/footclient.1.scd b/doc/footclient.1.scd index 7d89b9ed..365689af 100644 --- a/doc/footclient.1.scd +++ b/doc/footclient.1.scd @@ -73,6 +73,11 @@ terminal has terminated. The child process in the new terminal instance will use footclient's environment, instead of the server's. + Environment variables listed in the *Variables set in the child + process* section will be overwritten by the foot server. For + example, the new terminal will use *TERM* from the configuration, + not footclient's environment. + *-d*,*--log-level*={*info*,*warning*,*error*,*none*} Log level, used both for log output on stderr as well as syslog. Default: _warning_. @@ -163,9 +168,27 @@ fallback to the less specific path, with the following priority: This variable is set to *truecolor*, to indicate to client applications that 24-bit RGB colors are supported. +*PWD* + Current working directory (at the time of launching foot) + +*SHELL* + Set to the launched shell, if the shell is valid (it is listed in + */etc/shells*). + In addition to the variables listed above, custom environment variables may be defined in *foot.ini*(5). +## Variables *unset* in the child process + +*TERM_PROGRAM* +*TERM_PROGRAM_VERSION* + These environment variables are set by certain other terminal + emulators. We unset them, to prevent applications from + misdetecting foot. + +In addition to the variables listed above, custom environment +variables to unset may be defined in *foot.ini*(5). + # SEE ALSO *foot*(1) diff --git a/server.c b/server.c index ca55b8f3..7cc87152 100644 --- a/server.c +++ b/server.c @@ -332,7 +332,8 @@ 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, envp, &term_shutdown_handler, instance); + cdata.argc, argv, (const char *const *)envp, + &term_shutdown_handler, instance); if (instance->terminal == NULL) { LOG_ERR("failed to instantiate new terminal"); diff --git a/slave.c b/slave.c index 0cf64fa0..fa53c515 100644 --- a/slave.c +++ b/slave.c @@ -25,6 +25,11 @@ extern char **environ; +struct environ { + size_t count; + char **envp; +}; + #if defined(__FreeBSD__) static char * find_file_in_path(const char *file) @@ -303,9 +308,69 @@ err: _exit(errno); } +static bool +env_matches_var_name(const char *e, const char *name) +{ + const size_t e_len = strlen(e); + const size_t name_len = strlen(name); + + if (e_len <= name_len) + return false; + if (memcmp(e, name, name_len) != 0) + return false; + if (e[name_len] != '=') + return false; + return true; +} + +static void +add_to_env(struct environ *env, const char *name, const char *value) +{ + if (env->envp == NULL) + setenv(name, value, 1); + else { + char *e = xasprintf("%s=%s", name, value); + + /* Search for existing variable. If found, replace it with the + new value */ + for (size_t i = 0; i < env->count; i++) { + if (env_matches_var_name(env->envp[i], name)) { + free(env->envp[i]); + env->envp[i] = e; + return; + } + } + + /* If the variable does not already exist, add it */ + env->envp = xrealloc(env->envp, (env->count + 2) * sizeof(env->envp[0])); + env->envp[env->count++] = e; + env->envp[env->count] = NULL; + } +} + +static void +del_from_env(struct environ *env, const char *name) +{ + if (env->envp == NULL) + unsetenv(name); + else { + for (size_t i = 0; i < env->count; i++) { + if (env_matches_var_name(env->envp[i], name)) { + free(env->envp[i]); + memmove(&env->envp[i], + &env->envp[i + 1], + (env->count - i) * sizeof(env->envp[0])); + env->count--; + xassert(env->envp[env->count] == NULL); + break; + } + } + } +} + pid_t slave_spawn(int ptmx, int argc, const char *cwd, char *const *argv, - char *const *envp, const env_var_list_t *extra_env_vars, + const char *const *envp, const env_var_list_t *extra_env_vars, const char *term_env, const char *conf_shell, bool login_shell, const user_notifications_t *notifications) { @@ -350,15 +415,30 @@ slave_spawn(int ptmx, int argc, const char *cwd, char *const *argv, _exit(errno_copy); } - setenv("TERM", term_env, 1); - setenv("COLORTERM", "truecolor", 1); - setenv("PWD", cwd, 1); + /* Create a mutable copy of the environment */ + struct environ custom_env = {0}; + if (envp != NULL) { + for (const char *const *e = envp; *e != NULL; e++) + custom_env.count++; - unsetenv("TERM_PROGRAM"); - unsetenv("TERM_PROGRAM_VERSION"); + custom_env.envp = xcalloc( + custom_env.count + 1, sizeof(custom_env.envp[0])); + + size_t i = 0; + for (const char *const *e = envp; *e != NULL; e++, i++) + custom_env.envp[i] = xstrdup(*e); + xassert(custom_env.envp[custom_env.count] == NULL); + } + + add_to_env(&custom_env, "TERM", term_env); + add_to_env(&custom_env, "COLORTERM", "truecolor"); + add_to_env(&custom_env, "PWD", cwd); + + del_from_env(&custom_env, "TERM_PROGRAM"); + del_from_env(&custom_env, "TERM_PROGRAM_VERSION"); #if defined(FOOT_TERMINFO_PATH) - setenv("TERMINFO", FOOT_TERMINFO_PATH, 1); + add_to_env(&custom_envp, "TERMINFO", FOOT_TERMINFO_PATH); #endif if (extra_env_vars != NULL) { @@ -367,9 +447,9 @@ slave_spawn(int ptmx, int argc, const char *cwd, char *const *argv, const char *value = it->item.value; if (strlen(value) == 0) - unsetenv(name); + del_from_env(&custom_env, name); else - setenv(name, value, 1); + add_to_env(&custom_env, name, value); } } @@ -393,9 +473,10 @@ 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); + add_to_env(&custom_env, "SHELL", shell_argv[0]); - slave_exec(ptmx, shell_argv, envp != NULL ? envp : environ, + slave_exec(ptmx, shell_argv, + custom_env.envp != NULL ? custom_env.envp : environ, fork_pipe[1], login_shell, notifications); BUG("Unexpected return from slave_exec()"); break; diff --git a/slave.h b/slave.h index b1c08f14..26d93abb 100644 --- a/slave.h +++ b/slave.h @@ -7,7 +7,7 @@ #include "user-notification.h" pid_t slave_spawn( - int ptmx, int argc, const char *cwd, char *const *argv, char *const *envp, + int ptmx, int argc, const char *cwd, char *const *argv, const char *const *envp, const env_var_list_t *extra_env_vars, 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 efbbded1..5332ada0 100644 --- a/terminal.c +++ b/terminal.c @@ -1061,7 +1061,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, char *const *envp, + const char *token, int argc, char *const *argv, const char *const *envp, void (*shutdown_cb)(void *data, int exit_code), void *shutdown_data) { int ptmx = -1; diff --git a/terminal.h b/terminal.h index a8b65198..ddd4f1ea 100644 --- a/terminal.h +++ b/terminal.h @@ -728,7 +728,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, char *const *envp, + const char *token, int argc, char *const *argv, const char *const *envp, void (*shutdown_cb)(void *data, int exit_code), void *shutdown_data); bool term_shutdown(struct terminal *term);