Skip to content

Commit 6f41e0b

Browse files
authored
Merge pull request #5138 from grondo/path-insert
flux(1): avoid prepending to PATH when unnecessary
2 parents ca19cb0 + 049c96f commit 6f41e0b

File tree

6 files changed

+230
-14
lines changed

6 files changed

+230
-14
lines changed

src/cmd/flux.c

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <flux/optparse.h>
2323
#include <pwd.h>
2424

25+
#include "ccan/str/str.h"
2526
#include "src/common/libutil/log.h"
2627
#include "src/common/libutil/xzmalloc.h"
2728
#include "src/common/libutil/environment.h"
@@ -256,6 +257,41 @@ bool flux_is_installed (void)
256257
return (rc == 0);
257258
}
258259

260+
void ensure_self_first_in_path (struct environment *e, const char *selfdir)
261+
{
262+
const char *entry = NULL;
263+
char realdir[PATH_MAX+1];
264+
char path[PATH_MAX+1];
265+
266+
while ((entry = environment_var_next (e, "PATH", entry))) {
267+
/* Attempt to canonicalize path, skipping eny elements that
268+
* can't be resolved.
269+
*/
270+
if (!(realpath (entry, realdir)))
271+
continue;
272+
/* If this path matches "selfdir", then the current flux
273+
* executable already appears first in path. Return.
274+
*/
275+
if (streq (realdir, selfdir))
276+
return;
277+
/* Otherwise, check for a flux in this path element, if it
278+
* is present and executable, then the current flux is not
279+
* first in path, insert selfdir before this element.
280+
*/
281+
if (snprintf (path,
282+
sizeof (path),
283+
"%s/flux",
284+
realpath (entry, realdir)) >= sizeof (path)
285+
|| access (path, R_OK|X_OK) == 0) {
286+
if (environment_insert (e, "PATH", (char *) entry, selfdir) < 0)
287+
break;
288+
return;
289+
}
290+
}
291+
/* No flux(1) found in current PATH, we can insert selfdir at back */
292+
environment_push_back (e, "PATH", selfdir);
293+
}
294+
259295
/*
260296
* If flux command was run with relative or absolute path, then
261297
* prepend the directory for the flux executable to PATH. This
@@ -269,11 +305,11 @@ void setup_path (struct environment *env, const char *argv0)
269305
assert (argv0);
270306

271307
/* If argv[0] was explicitly "flux" then assume PATH is already set */
272-
if (strcmp (argv0, "flux") == 0)
308+
if (streq (argv0, "flux"))
273309
return;
274310
if ((selfdir = executable_selfdir ())) {
275311
environment_from_env (env, "PATH", "/bin:/usr/bin", ':');
276-
environment_push (env, "PATH", executable_selfdir ());
312+
ensure_self_first_in_path (env, selfdir);
277313
}
278314
else
279315
log_msg_exit ("Unable to determine flux executable dir");

src/common/libutil/Makefile.am

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,8 @@ TESTS = test_sha1.t \
131131
test_hola.t \
132132
test_strstrip.t \
133133
test_slice.t \
134-
test_timestamp.t
134+
test_timestamp.t \
135+
test_environment.t
135136

136137
test_ldadd = \
137138
$(top_builddir)/src/common/libutil/libutil.la \
@@ -278,3 +279,7 @@ test_slice_t_LDADD = $(test_ldadd)
278279
test_timestamp_t_SOURCES = test/timestamp.c
279280
test_timestamp_t_CPPFLAGS = $(test_cppflags)
280281
test_timestamp_t_LDADD = $(test_ldadd)
282+
283+
test_environment_t_SOURCES = test/environment.c
284+
test_environment_t_CPPFLAGS = $(test_cppflags)
285+
test_environment_t_LDADD = $(test_ldadd)

src/common/libutil/environment.c

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,6 @@ static void environment_push_inner (struct environment *e,
162162
split_value = rev;
163163
split_value_len = rev_len;
164164
}
165-
166165
entry = NULL;
167166
while((entry = argz_next (split_value, split_value_len, entry))) {
168167
char *found;
@@ -255,6 +254,38 @@ const char *environment_cursor (struct environment *e)
255254
return zhash_cursor (e->environment);
256255
}
257256

257+
const char *environment_var_next (struct environment *e,
258+
const char *key,
259+
const char *entry)
260+
{
261+
struct env_item *item = zhash_lookup (e->environment, key);
262+
if (!item)
263+
return NULL;
264+
return argz_next (item->argz, item->argz_len, entry);
265+
}
266+
267+
int environment_insert (struct environment *e,
268+
const char *key,
269+
char *before,
270+
const char *value)
271+
{
272+
error_t err;
273+
struct env_item *item = zhash_lookup (e->environment, key);
274+
if (!item) {
275+
errno = ENOENT;
276+
return -1;
277+
}
278+
if ((err = argz_insert (&item->argz,
279+
&item->argz_len,
280+
before,
281+
value) != 0)) {
282+
errno = err;
283+
return -1;
284+
}
285+
return 0;
286+
}
287+
288+
258289
void environment_apply (struct environment *e)
259290
{
260291
const char *key;

src/common/libutil/environment.h

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,19 @@ const char *environment_next (struct environment *e);
4848
*/
4949
const char *environment_cursor (struct environment *e);
5050

51+
/**
52+
* @brief Iterate over a multi-element environment variable *_key
53+
*
54+
* @param e the environment to operate on
55+
* @param key the environment variable to iterate over
56+
* @param entry the current entry, set to NULL to begin iteration
57+
*
58+
* @return The value of the current environment variable element
59+
*/
60+
const char *environment_var_next (struct environment *e,
61+
const char *key,
62+
const char *entry);
63+
5164
/**
5265
* @brief Apply the changes encoded in this environment to the environment of
5366
* the current process.
@@ -86,6 +99,22 @@ void environment_push_back (struct environment *e,
8699
const char *key,
87100
const char *value);
88101

102+
/**
103+
* @brief Push "value" before position "before" in the environment variable
104+
* "key."
105+
*
106+
* @param e the environment in which to add this element
107+
* @param key the environment variable name
108+
* @param before the element before which to insert this value
109+
* @param value the value to insert
110+
*
111+
* @return 0 on success -1 with errno set on failure.
112+
*/
113+
int environment_insert (struct environment *e,
114+
const char *key,
115+
char *before,
116+
const char *value);
117+
89118
/**
90119
* @brief Add the specified value to the front of the target key without
91120
* de-duplication, it will still be separated from the rest of the value by sep,
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/************************************************************\
2+
* Copyright 2023 Lawrence Livermore National Security, LLC
3+
* (c.f. AUTHORS, NOTICE.LLNS, COPYING)
4+
*
5+
* This file is part of the Flux resource manager framework.
6+
* For details, see https://github.com/flux-framework.
7+
*
8+
* SPDX-License-Identifier: LGPL-3.0
9+
\************************************************************/
10+
11+
#include <errno.h>
12+
13+
#include "ccan/str/str.h"
14+
#include "src/common/libtap/tap.h"
15+
#include "src/common/libutil/environment.h"
16+
17+
static void test_var_next ()
18+
{
19+
const char *entry = NULL;
20+
struct environment *e = environment_create ();
21+
if (e == NULL)
22+
BAIL_OUT ("Failed to create environment object!");
23+
ok (environment_var_next (e, "PATH", NULL) == NULL,
24+
"environment_var_next () returns NULL for missing env var");
25+
environment_set (e, "PATH", "/bin:/usr/bin:/usr/local/bin", ':');
26+
diag ("set PATH=/bin:/usr/bin:/usr/local/bin");
27+
ok ((entry = environment_var_next (e, "PATH", entry)) != NULL,
28+
"environment_var_next () works");
29+
is (entry, "/bin",
30+
"environment_var_next returns first element");
31+
ok ((entry = environment_var_next (e, "PATH", entry)) != NULL,
32+
"environment_var_next () works");
33+
is (entry, "/usr/bin",
34+
"environment_var_next returns next element");
35+
ok ((entry = environment_var_next (e, "PATH", entry)) != NULL,
36+
"environment_var_next () works");
37+
is (entry, "/usr/local/bin",
38+
"environment_var_next returns last element");
39+
ok (!(entry = environment_var_next (e, "PATH", entry)),
40+
"environment_var_next () returns NULL after last element");
41+
environment_destroy (e);
42+
}
43+
44+
static void test_insert ()
45+
{
46+
const char *entry = NULL;
47+
struct environment *e = environment_create ();
48+
if (e == NULL)
49+
BAIL_OUT ("Failed to create environment object!");
50+
ok (environment_insert (e, "PATH", "/bin", "/foo") < 0 && errno == ENOENT,
51+
"environment_insert on missing key returns ENOENT");
52+
environment_set (e, "PATH", "/bin:/usr/bin:/usr/local/bin", ':');
53+
diag ("set PATH=/bin:/usr/bin:/usr/local/bin");
54+
diag ("searching for entry=/usr/bin");
55+
while ((entry = environment_var_next (e, "PATH", entry)))
56+
if (streq (entry, "/usr/bin"))
57+
break;
58+
diag ("entry=%s", entry);
59+
ok (environment_insert (e, "PATH", (char *) entry, "/new/path") == 0,
60+
"environment_insert /new/path before /usr/bin return success");
61+
is (environment_get (e, "PATH"),
62+
"/bin:/new/path:/usr/bin:/usr/local/bin",
63+
"PATH is now /bin:/new/path:/usr/bin:/usr/local/bin");
64+
environment_destroy (e);
65+
}
66+
67+
int main (int argc, char *argv[])
68+
{
69+
plan (NO_PLAN);
70+
71+
test_var_next ();
72+
test_insert ();
73+
74+
done_testing ();
75+
76+
return 0;
77+
};
78+
79+
/*
80+
* vi:tabstop=4 shiftwidth=4 expandtab
81+
*/

t/t1102-cmddriver.t

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -82,16 +82,50 @@ test_expect_success 'cmddriver removes multiple contiguous separators in input'
8282
flux env sh -c 'echo \$LUA_PATH' | grep -v ';;;;')
8383
"
8484
readlink --version >/dev/null && test_set_prereq READLINK
85-
test_expect_success READLINK 'cmddriver adds its own path to PATH if called with relative path' "
86-
fluxcmd=\$(readlink -f \$(which flux)) &&
87-
fluxdir=\$(dirname \$fluxcmd) &&
88-
PATH='/bin:/usr/bin' \$fluxcmd env sh -c 'echo \$PATH' | grep ^\$fluxdir
89-
"
90-
test_expect_success READLINK 'cmddriver moves its own path to the front of PATH' "
91-
fluxcmd=\$(readlink -f \$(which flux)) &&
92-
fluxdir=\$(dirname \$fluxcmd) &&
93-
PATH=/bin:\$fluxdir \$fluxcmd env sh -c 'echo \$PATH' | grep ^\$fluxdir
94-
"
85+
86+
87+
# N.B.: In the tests below we need to ensure that /bin:/usr/bin appear
88+
# in PATH so that core utilities like `ls` can be found by the libtool
89+
# wrappers or other processes invoked by calling `flux`. However, this
90+
# means the results of the test can be influenced by whether or not a
91+
# flux executable appears in /bin or /usr/bin. Therefore, care must be
92+
# taken to ensure consistent results no matter what is in these paths.
93+
#
94+
# Ensure a bogus 'flux' executable occurs first in path, then make sure
95+
# command -v flux finds the right flux:
96+
#
97+
test_expect_success 'cmddriver adds its own path to PATH' '
98+
mkdir bin &&
99+
cat <<-EOF >bin/flux &&
100+
#!/bin/sh
101+
/bin/true
102+
EOF
103+
chmod +x bin/flux &&
104+
fluxcmd=$(command -v flux) &&
105+
result=$(PATH=$(pwd)/bin:/bin:/usr/bin \
106+
$fluxcmd env sh -c "command -v flux") &&
107+
test_debug "echo result=$result" &&
108+
test "$result" = "$fluxcmd"
109+
'
110+
# Use bogus flux in PATH and ensure flux cmddriver inserts its own path
111+
# just before this path, not at front of PATH.
112+
test_expect_success 'cmddriver inserts its path at end of PATH' '
113+
fluxdir=$(dirname $fluxcmd) &&
114+
result=$(PATH=/foo:$(pwd)/bin:/bin:/usr/bin \
115+
$fluxcmd env printenv PATH) &&
116+
test_debug "echo result=$result" &&
117+
test "$result" = "/foo:$fluxdir:$(pwd)/bin:/bin:/usr/bin"
118+
'
119+
# Ensure a PATH that already returns current flux first is not modified
120+
# by flux(1)
121+
test_expect_success READLINK 'cmddriver does not adjust PATH if unnecessary' '
122+
fluxdir=$(dirname $fluxcmd) &&
123+
printenv=$(command -v printenv) &&
124+
mypath=/foo:/bar:$fluxdir:/usr/bin:/bin &&
125+
newpath=$(PATH=$mypath $fluxcmd env $printenv PATH) &&
126+
test_debug "echo PATH=$newpath" &&
127+
test "$newpath" = "$mypath"
128+
'
95129
test_expect_success 'FLUX_*_PREPEND environment variables work' '
96130
( FLUX_CONNECTOR_PATH_PREPEND=/foo \
97131
flux /usr/bin/printenv | grep "FLUX_CONNECTOR_PATH=/foo" &&

0 commit comments

Comments
 (0)