Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions src/cli/cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -175,16 +175,26 @@ const char *cbm_find_cli(const char *name, const char *home_dir) {
char path_copy[4096];
snprintf(path_copy, sizeof(path_copy), "%s", path_env);
char *saveptr;
#ifdef _WIN32
const char *path_sep = ";";
#else
const char *path_sep = ":";
#endif
// NOLINTNEXTLINE(misc-include-cleaner) — strtok_r provided by standard header
char *dir = strtok_r(path_copy, ":", &saveptr);
char *dir = strtok_r(path_copy, path_sep, &saveptr);
while (dir) {
snprintf(buf, sizeof(buf), "%s/%s", dir, name);
struct stat st;
#ifdef _WIN32
/* On Windows, S_IXUSR is not meaningful — just check file exists */
if (stat(buf, &st) == 0) {
#else
// NOLINTNEXTLINE(misc-include-cleaner) — S_IXUSR provided by standard header
if (stat(buf, &st) == 0 && (st.st_mode & S_IXUSR)) {
#endif
return buf;
}
dir = strtok_r(NULL, ":", &saveptr);
dir = strtok_r(NULL, path_sep, &saveptr);
}
}

Expand Down Expand Up @@ -214,7 +224,11 @@ const char *cbm_find_cli(const char *name, const char *home_dir) {
continue;
}
struct stat st;
#ifdef _WIN32
if (stat(paths[i], &st) == 0) {
#else
if (stat(paths[i], &st) == 0 && (st.st_mode & S_IXUSR)) {
#endif
snprintf(buf, sizeof(buf), "%s", paths[i]);
return buf;
}
Expand Down
19 changes: 12 additions & 7 deletions tests/test_cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -315,18 +315,18 @@ TEST(cli_find_cli_not_found) {
}

TEST(cli_find_cli_on_path) {
#ifdef _WIN32
SKIP("PATH search differs on Windows");
#endif
/* Port of TestFindCLI_FoundOnPATH */
char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-find-XXXXXX");
if (!cbm_mkdtemp(tmpdir))
SKIP("cbm_mkdtemp failed");

/* Create a fake CLI binary (no extension — like npm shims on Windows) */
char fakecli[512];
snprintf(fakecli, sizeof(fakecli), "%s/fakecli", tmpdir);
write_test_file(fakecli, "#!/bin/sh\n");
#ifndef _WIN32
chmod(fakecli, 0500);
#endif

const char *raw = getenv("PATH");
char *old_path = raw ? strdup(raw) : NULL;
Expand All @@ -346,9 +346,6 @@ TEST(cli_find_cli_on_path) {
}

TEST(cli_find_cli_fallback_paths) {
#ifdef _WIN32
SKIP("shell scripts + chmod not available on Windows");
#endif
/* Port of TestFindCLI_FallbackPaths */
char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-find-XXXXXX");
if (!cbm_mkdtemp(tmpdir))
Expand All @@ -358,17 +355,25 @@ TEST(cli_find_cli_fallback_paths) {
snprintf(localbin, sizeof(localbin), "%s/.local/bin", tmpdir);
test_mkdirp(localbin);

/* Create a fake CLI binary in fallback location (no extension) */
char fakecli[512];
snprintf(fakecli, sizeof(fakecli), "%s/testcli", localbin);
write_test_file(fakecli, "#!/bin/sh\n");
#ifndef _WIN32
chmod(fakecli, 0500);
#endif

const char *raw = getenv("PATH");
char *old_path = raw ? strdup(raw) : NULL;
#ifdef _WIN32
cbm_setenv("PATH", "C:\\nonexistent", 1);
#else
cbm_setenv("PATH", "/nonexistent", 1);
#endif

const char *result = cbm_find_cli("testcli", tmpdir);
ASSERT_STR_EQ(result, fakecli);
ASSERT(result[0] != '\0');
ASSERT(strstr(result, "testcli") != NULL);

if (old_path) {
cbm_setenv("PATH", old_path, 1);
Expand Down