This code's not quite what you want, as it simply blindly executes the first thing it comes to. But you can modify the search code so that instead of calling execve
you call access
and then stat
to find out if it's not a directory. I think only the last function, execvepath
, has to be replaced.
In the best Unix tradition, the code is "self-documenting" (i.e., undocumented).
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include "shellpath.h"
static void *malloc_check(const char *what, size_t n) {
void *p = malloc(n);
if (p == NULL) {
fprintf(stderr, "Cannot allocate %zu bytes to %s\n", n, what);
exit(2);
}
return p;
}
static char *strsave(const char *s, const char *lim) {
if (lim == NULL)
lim = s + strlen(s);
char *p = malloc_check("save string", lim - s + 1);
strncpy(p, s, lim-s);
p[lim-s] = '\0';
return p;
}
char ** shellpath(void) {
const char *path = getenv("PATH");
if (!path)
path = "/bin:/usr/bin:/usr/local/bin";
char **vector = // size is overkill
malloc_check("hold path elements", strlen(path) * sizeof(*vector));
const char *p = path;
int next = 0;
while (p) {
char *q = strchr(p, ':');
vector[next++] = strsave(p, q);
p = q ? q + 1 : NULL;
}
vector[next] = NULL;
return vector;
}
void freeshellpath (char *shellpath[]) {
for (int i = 0; shellpath[i]; i++)
free(shellpath[i]);
free(shellpath);
}
unsigned maxpathlen(char *path[], const char *base) {
unsigned blen = strlen(base);
unsigned n = 0;
for (int i = 0; path[i]; i++) {
unsigned pn = strlen(path[i]);
if (pn > n) n = pn;
}
return blen+n+1;
}
void execvepath(char *path[], const char *base, char *const argv[],
char *const envp[])
{
if (strchr(base, '/'))
execve(base, argv, envp);
else {
size_t maxlen = maxpathlen(path, base)+1;
char *buf = malloc_check("hold path", maxlen);
for (int i = 0; path[i]; i++) {
snprintf(buf, maxlen, "%s/%s", path[i], base);
execve(buf, argv, envp);
}
}
}