Skip to content

Commit

Permalink
Implement environment variable caching for pl_env
Browse files Browse the repository at this point in the history
  • Loading branch information
pgaskin authored and flyingmutant committed Jul 15, 2023
1 parent 3334ca7 commit 4ecbf4f
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 81 deletions.
154 changes: 80 additions & 74 deletions pl_env.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,45 +25,25 @@
#include <stdlib.h>
#include <string.h>

extern char **environ;
static char **pl_env_cache = NULL;

static bool pl_env_contains_delimiter(const char *str)
{
return !!strchr(str, PL_ENV_DELIMITER);
}

/**
* pl_getenv_normalized gets an env var and puts it into the required format for
* pl_env_norm gets an env var and puts it into the required format for
* pl_env_reduce and pl_env_expand, which does exact string matching/replacement
* against the file paths. It converts backslashes to slashes on Windows,
* removes consecutive slashes, removes './' path segments, simplifies '../'
* path segments (and returns NULL if it would result in going above the
* uppermost directory), and removes the trailing slash. In addition, it trims
* the variable name before looking it up.
*/
static char *pl_env_getenv_normalized(const char *var)
static char *pl_env_norm(char *path)
{
/* duplicate the var */
char *var_d = xstrdup(var);

/* trim the var */
char *var_s = var_d;
char *var_e = var_d + strlen(var_d);
while (var_s < var_e && isspace(*var_s)) var_s++;
while (var_e > var_s && isspace(*(var_e-1))) var_e--;
*var_e = '\0';

/* if it's empty, return NULL */
if (var_e-var_s == 0) return NULL;

/* get the var */
char *val = getenv(var_s);
free(var_d);

/* if it's nonexistent or empty, return NULL */
if (!val || !*val) return NULL;

/* duplicate the value */
char *new = xstrdup(val);

#ifdef _WIN32
/* convert backslashes to slashes */
/* note: cmus uses forward slashes internally, but Windows accepts both */
Expand All @@ -74,58 +54,111 @@ static char *pl_env_getenv_normalized(const char *var)

/* canonicalize the path in-place */
size_t r = 1, w = 1;
while (new[r]) {
while (path[r]) {

/* handle the start of a segment */
if (w == 0 || new[w-1] == '/') {
if (w == 0 || path[w-1] == '/') {

/* handle empty segments */
if (new[r] == '/') {
if (path[r] == '/') {

/* skip the duplicate slashes */
while (new[r] == '/') r++;
while (path[r] == '/') r++;

continue;
}

/* handle '.' segments */
if (new[r] == '.' && (new[r+1] == '/' || !new[r+1])) {
if (path[r] == '.' && (path[r+1] == '/' || !path[r+1])) {

/* skip them */
if (new[r += 1]) r++;
if (path[r += 1]) r++;

continue;
}

/* handle '..' segments */
if (new[r] == '.' && new[r+1] == '.' && (new[r+2] == '/' || !new[r+2])) {
if (path[r] == '.' && path[r+1] == '.' && (path[r+2] == '/' || !path[r+2])) {

/* if there aren't any parent directories left to skip, return NULL */
if (!w) return NULL;

/* remove the previous segment up to the '/' */
for (w--; w && new[w-1] != '/'; ) w--;
for (w--; w && path[w-1] != '/'; ) w--;

/* skip the '..' */
if (new[r += 2]) r++;
if (path[r += 2]) r++;

continue;
}
}

/* write the next character */
new[w++] = new[r++];
path[w++] = path[r++];
}

/* remove the trailing slash if the path isn't / */
if (w >= 2 && new[w-1] == '/') {
if (w >= 2 && path[w-1] == '/') {
w--;
}

/* terminate the path */
new[w] = '\0';
path[w] = '\0';

return new;
return path;
}

/**
* pl_env_get is like getenv, but it allows using non-null-terminated variable
* names, trims the variable name, ensures the environment variable doesn't
* contain the marker used by pl_env, and normalizes the paths in environment
* variables with pl_env_norm.
*/
static const char *pl_env_get(const char *var, int var_len)
{
if (!var)
return NULL;

size_t vl = var_len == -1
? strlen(var)
: var_len;

const char *vs = var;
const char *ve = var + vl;
while (vs < ve && isspace(*vs)) vs++;
while (ve > vs && isspace(*(ve-1))) ve--;
vl = ve-vs;

if (!vl)
return NULL;

for (const char *c = vs; c < ve; c++)
if (*c == PL_ENV_DELIMITER || *c == '=')
return NULL;

for (char **x = pl_env_cache; x && *x; x++)
if (strncmp(*x, vs, vl) == 0 && (*x)[vl] == '=')
return *x + vl + 1;

return NULL;
}

void pl_env_init(void)
{
for (char **x = pl_env_cache; x && *x; x++)
free(*x);
free(pl_env_cache);

size_t n = 0;
for (char **x = environ; *x; x++)
n++;

char **new = pl_env_cache = xnew(char*, n+1);
for (char **x = environ; *x; x++)
if (!pl_env_contains_delimiter(*x))
if (!pl_env_norm(strchr((*new++ = xstrdup(*x)), '=') + 1))
free(*--new);
*new = NULL;
}

char *pl_env_reduce(const char *path)
Expand All @@ -134,37 +167,27 @@ char *pl_env_reduce(const char *path)
return xstrdup(path);

for (char **var = pl_env_vars; *var && **var; var++) {
char *val = pl_env_getenv_normalized(*var);
const char *val = pl_env_get(*var, -1);
if (!val)
continue;
if (!*val || pl_env_contains_delimiter(val)) {
free(val);
continue;
}

size_t val_len = strlen(val);

#ifdef _WIN32
if (strncasecmp(path, val, val_len) != 0) {
free(val);
if (strncasecmp(path, val, val_len) != 0)
continue;
}
#else
if (strncmp(path, val, val_len) != 0) {
free(val);
if (strncmp(path, val, val_len) != 0)
continue;
}
#endif

const char *rem = path + val_len;

/* always keep the slash at the beginning of the path, and only
use the env var if it replaces an entire path component (i.e.
it is a directory) */
if (*rem != '/') {
free(val);
if (*rem != '/')
continue;
}

size_t var_len = strlen(*var);
size_t rem_len = strlen(rem);
Expand All @@ -179,7 +202,6 @@ char *pl_env_reduce(const char *path)
ptr += rem_len;
*ptr = '\0';

free(val);
return new;
}

Expand All @@ -191,20 +213,16 @@ char *pl_env_expand(const char *path)
if (!path)
return NULL;

const char *rem;
char *var = pl_env_var_str(path, &rem);
if (!var)
int len;
const char *var;
if (!(var = pl_env_var(path, &len)))
return xstrdup(path);

char *val = pl_env_getenv_normalized(var);
free(var);
const char *val = pl_env_get(var, len);
if (!val)
return xstrdup(path);
if (!*val || pl_env_contains_delimiter(val)) {
free(val);
return xstrdup(path);
}

const char *rem = pl_env_var_remainder(path, len);
size_t val_len = strlen(val);
size_t rem_len = strlen(rem);

Expand All @@ -216,7 +234,6 @@ char *pl_env_expand(const char *path)
ptr += rem_len;
*ptr = '\0';

free(val);
return new;
}

Expand All @@ -237,17 +254,6 @@ const char *pl_env_var_remainder(const char *path, int length)
return path+length+2;
}

char *pl_env_var_str(const char *path, const char **remainder)
{
int len;
const char *var;
if (!(var = pl_env_var(path, &len)))
return NULL;
if (remainder)
*remainder = pl_env_var_remainder(path, len);
return xstrndup(var, len);
}

int pl_env_var_len(const char *path)
{
int len;
Expand Down
13 changes: 6 additions & 7 deletions pl_env.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,12 @@
*/
#define PL_ENV_DELIMITER '\x1F'

/**
* pl_env_init initializes the environment variable cache used by pl_env_get. It
* must be called before loading the library, playlists, or cache.
*/
void pl_env_init(void);

/**
* pl_env_reduce checks the base path against the configured environment
* variables, replaces the first match with a substitution, and returns a
Expand Down Expand Up @@ -204,13 +210,6 @@ const char *pl_env_var(const char *path, int *out_length);
*/
const char *pl_env_var_remainder(const char *path, int length);

/**
* pl_env_var_str is like pl_env_var, but it returns a malloc'd null-terminated
* string instead. If remainder is not NULL, it is set to point to the remainder
* of the path.
*/
char *pl_env_var_str(const char *path, const char **remainder);

/**
* pl_env_var_len returns the length of the substituted environment variable
* name, if present. Otherwise, it returns 0.
Expand Down
4 changes: 4 additions & 0 deletions ui_curses.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
#include "mixer.h"
#include "mpris.h"
#include "locking.h"
#include "pl_env.h"
#ifdef HAVE_CONFIG
#include "config/curses.h"
#include "config/iconv.h"
Expand Down Expand Up @@ -2344,6 +2345,9 @@ static void init_all(void)
/* plugins have been loaded so we know what plugin options are available */
options_add();

/* cache the normalized env vars for pl_env */
pl_env_init();

lib_init();
searchable = tree_searchable;
cmus_init();
Expand Down

0 comments on commit 4ecbf4f

Please sign in to comment.