session: process environment.d and allow empty variables

1. All '*.env' files in an 'environment.d' directory alongside each
   potential 'environment' file will be parsed and added to the
   environment.

2. For the purposes of configuration merging, an environment definition
   exists at one level if either the 'environment' file is defined or
   its corresponding 'environment.d' contains any valid '*.env' file.

3. Variable declarations of the form "VARIABLE=", with no following
   value, will be written to the environment as empty strings.
This commit is contained in:
Andrew J. Hesford 2024-03-09 12:03:30 -05:00 committed by Johan Malm
parent 52cb643189
commit e837445114
4 changed files with 121 additions and 10 deletions

View file

@ -37,13 +37,23 @@ option.
All configuration and theme files except autostart and shutdown are re-loaded on
receiving signal SIGHUP.
The *environment* file is parsed as *variable=value* and sets environment
variables accordingly. It is recommended to specify keyboard layout settings and
cursor size/theme here; see environment variable section below for details. Note
that the environment file is treated differently by openbox where it is simply
sourced prior to running openbox.
Note: Tilde (~) and environment variables in the value are expanded, but
subshell syntax and apostrophes are ignored.
Environment variables may be set within *environment* files, wherein each line
defines shell variables in the format *variable=value*. It is recommended to
specify keyboard layout settings and cursor size/theme here; see environment
variable section below for details. Within an XDG Base Directory, a file named
"environment" will be parsed first, followed by any file matching the glob
"environment.d/\*.env". Files within the environment.d directory are parsed in
an arbitrary order; any variables that must be set in a particular sequence
should be set within the same file. Unless the --merge-config option is
specified, labwc will consider a particular XDG Base Directory to have provided
an environment file if that directory contains either the "environment"
directory or at least one "environment.d/\*.env" file.
Note: environment files are treated differently by Openbox, which will simply
source the file as a valid shell script before running the window manager. Files
are instead parsed directly by labwc, although any environment variables
referenced as $VARIABLE or ${VARIABLE} will be substituted and the tilde (~)
will be expanded as the user's home directory.
The *autostart* file is executed as a shell script after labwc has read its
configuration and set variables defined in the environment file. Additionally,

View file

@ -59,7 +59,18 @@ char *strdup_printf(const char *fmt, ...);
* The separator is arbitrary. When the separator is NULL, a single space will
* be used.
*/
char *str_join(const char * const parts[],
char *str_join(const char *const parts[],
const char *restrict fmt, const char *restrict sep);
/**
* str_endswith - indicate whether a string ends with a given suffix
* @string: string to test
* @suffix: suffix to expect in string
*
* If suffix is "" or NULL, this method always returns true; otherwise, this
* method returns true if and only if the full suffix exists at the end of the
* string.
*/
bool str_endswith(const char *const string, const char *const suffix);
#endif /* LABWC_STRING_HELPERS_H */

View file

@ -86,7 +86,7 @@ strdup_printf(const char *fmt, ...)
}
char *
str_join(const char * const parts[],
str_join(const char *const parts[],
const char *restrict fmt, const char *restrict sep)
{
assert(parts);
@ -154,3 +154,19 @@ str_join(const char * const parts[],
return buf;
}
bool
str_endswith(const char *const string, const char *const suffix)
{
size_t len_str = string ? strlen(string) : 0;
size_t len_sfx = suffix ? strlen(suffix) : 0;
if (len_str < len_sfx) {
return false;
}
if (len_sfx == 0) {
return true;
}
return strcmp(string + len_str - len_sfx, suffix) == 0;
}

View file

@ -1,11 +1,13 @@
// SPDX-License-Identifier: GPL-2.0-only
#define _POSIX_C_SOURCE 200809L
#include <assert.h>
#include <dirent.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <wlr/backend/drm.h>
#include <wlr/backend/multi.h>
#include <wlr/util/log.h>
@ -49,9 +51,10 @@ process_line(char *line)
buf_add(&value, string_strip(++p));
buf_expand_shell_variables(&value);
buf_expand_tilde(&value);
if (string_null_or_empty(key) || !value.len) {
if (string_null_or_empty(key)) {
goto error;
}
setenv(key, value.buf, 1);
error:
free(value.buf);
@ -80,6 +83,71 @@ read_environment_file(const char *filename)
return true;
}
static char *
strdup_env_path_validate(const char *prefix, struct dirent *dirent)
{
assert(prefix);
/* Valid environment files always end in '.env' */
if (!str_endswith(dirent->d_name, ".env")) {
return NULL;
}
char *full_path = strdup_printf("%s/%s", prefix, dirent->d_name);
if (!full_path) {
return NULL;
}
/* Valid environment files must be regular files */
struct stat statbuf;
if (stat(full_path, &statbuf) == 0 && S_ISREG(statbuf.st_mode)) {
return full_path;
}
free(full_path);
return NULL;
}
static bool
read_environment_dir(const char *path_prefix)
{
bool success = false;
char *path = strdup_printf("%s.d", path_prefix);
errno = 0;
DIR *envdir = opendir(path);
if (!envdir) {
if (errno != ENOENT) {
const char *err_msg = strerror(errno);
wlr_log(WLR_INFO,
"failed to read environment directory: %s",
err_msg ? err_msg : "reason unknown");
}
goto env_dir_cleanup;
}
struct dirent *dirent;
while ((dirent = readdir(envdir)) != NULL) {
char *env_file_path = strdup_env_path_validate(path, dirent);
if (!env_file_path) {
continue;
}
if (read_environment_file(env_file_path)) {
success = true;
}
free(env_file_path);
}
env_dir_cleanup:
closedir(envdir);
free(path);
return success;
}
static void
backend_check_drm(struct wlr_backend *backend, void *is_drm)
{
@ -176,7 +244,13 @@ session_environment_init(void)
for (struct wl_list *elm = iter(&paths); elm != &paths; elm = iter(elm)) {
struct path *path = wl_container_of(elm, path, link);
/* Process an environment file itself */
bool success = read_environment_file(path->string);
/* Process a correponding environment.d directory */
success |= read_environment_dir(path->string);
if (success && !should_merge_config) {
break;
}