feat: extend shell with shellExpandRedirect and shellFindCommandSubst
This commit is contained in:
1 parent
6bc12b43a7
commit
ff86a70128
1 file changed
+173
+173
@@ -365,6 +365,179 @@ static void shellExpandArgv(char** argv, int argc, char storage[][envEntryMax])
|
|||||||
unexpanded (unlike every other argv position, which shellExpandArgv
|
unexpanded (unlike every other argv position, which shellExpandArgv
|
||||||
already covered) -- an honestly-scoped gap flagged when `$NAME`
|
already covered) -- an honestly-scoped gap flagged when `$NAME`
|
||||||
expansion was first added, closed here. Each field gets its own
|
expansion was first added, closed here. Each field gets its own
|
||||||
|
dedicated storage slot since all four could need to be expanded on the
|
||||||
|
SAME command line at once (e.g. `cmd < $IN > $OUT 2> $ERR`). */
|
||||||
|
static void shellExpandRedirect(ShellRedirect* redirect, char storage[4][envEntryMax])
|
||||||
|
{
|
||||||
|
if (redirect->inPath)
|
||||||
|
redirect->inPath = shellExpandToken(redirect->inPath, storage[0], envEntryMax);
|
||||||
|
if (redirect->outPath)
|
||||||
|
redirect->outPath = shellExpandToken(redirect->outPath, storage[1], envEntryMax);
|
||||||
|
if (redirect->errPath)
|
||||||
|
redirect->errPath = shellExpandToken(redirect->errPath, storage[2], envEntryMax);
|
||||||
|
if (redirect->hereString)
|
||||||
|
redirect->hereString = shellExpandToken(redirect->hereString, storage[3], envEntryMax);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool shellFindCommandSubst(const char* line, int* startOut, int* endOut, char* cmdOut, int cmdCap)
|
||||||
|
{
|
||||||
|
const char* open = strstr(line, "$(");
|
||||||
|
if (!open) {
|
||||||
|
*startOut = -1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* close = strchr(open + 2, ')');
|
||||||
|
if (!close)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int len = (int)(close - (open + 2));
|
||||||
|
if (len > cmdCap - 1)
|
||||||
|
len = cmdCap - 1;
|
||||||
|
memcpy(cmdOut, open + 2, (uint64)len);
|
||||||
|
cmdOut[len] = 0;
|
||||||
|
|
||||||
|
*startOut = (int)(open - line);
|
||||||
|
*endOut = (int)(close - line) + 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool shellGlobMatch(const char* pattern, const char* name)
|
||||||
|
{
|
||||||
|
const char* p = pattern;
|
||||||
|
const char* n = name;
|
||||||
|
const char* starPattern = nullptr;
|
||||||
|
const char* starName = nullptr;
|
||||||
|
|
||||||
|
while (*n) {
|
||||||
|
if (*p == '?' || *p == *n) {
|
||||||
|
p++;
|
||||||
|
n++;
|
||||||
|
} else if (*p == '*') {
|
||||||
|
starPattern = p++;
|
||||||
|
starName = n;
|
||||||
|
} else if (starPattern) {
|
||||||
|
p = starPattern + 1;
|
||||||
|
n = ++starName;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (*p == '*')
|
||||||
|
p++;
|
||||||
|
return *p == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool shellTokenHasGlob(const char* token)
|
||||||
|
{
|
||||||
|
for (const char* c = token; *c; c++)
|
||||||
|
if (*c == '*' || *c == '?')
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void shellSplitGlobToken(const char* token, char* dirOut, int dirCap, char* patternOut, int patternCap)
|
||||||
|
{
|
||||||
|
const char* lastSlash = nullptr;
|
||||||
|
for (const char* c = token; *c; c++)
|
||||||
|
if (*c == '/')
|
||||||
|
lastSlash = c;
|
||||||
|
|
||||||
|
if (!lastSlash) {
|
||||||
|
if (dirCap > 0)
|
||||||
|
dirOut[0] = 0;
|
||||||
|
snprintf(patternOut, (uint64)patternCap, "%s", token);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dirLen = (int)(lastSlash - token) + 1;
|
||||||
|
if (dirLen >= dirCap)
|
||||||
|
dirLen = dirCap - 1;
|
||||||
|
memcpy(dirOut, token, (uint64)dirLen);
|
||||||
|
dirOut[dirLen] = 0;
|
||||||
|
|
||||||
|
snprintf(patternOut, (uint64)patternCap, "%s", lastSlash + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Storage capacity for one shellExpandGlobs match's resolved path -- a
|
||||||
|
smaller, dedicated constant rather than reusing pathCapacity, the same
|
||||||
|
"arbitrary but documented" fixed-size trade-off shellExpandArgv's own
|
||||||
|
envEntryMax already makes for a $NAME value, since this buffer gets
|
||||||
|
multiplied by shellMaxArgs (and, in runPipelineStages, by
|
||||||
|
shellMaxPipelineStages again on top of that) -- see shellExpandGlobs'
|
||||||
|
own doc comment for why that multiplication matters for a 16KiB kernel
|
||||||
|
thread stack (sched.c's threadStackSize). */
|
||||||
|
#define shellGlobPathCapacity 64
|
||||||
|
|
||||||
|
/* Expands any argv[i] (i in [0,argc)) containing a glob metacharacter into
|
||||||
|
the real directory entries matching it (VFS child-list order, vfsChild/
|
||||||
|
vfsNext -- the same traversal listDir's own `ls` builtin above already
|
||||||
|
uses), replacing that one token with however many real paths matched,
|
||||||
|
written into `storage` (one shellGlobPathCapacity-byte slot per FINAL
|
||||||
|
argv position, not per original token) and pointed at from `argv` in
|
||||||
|
place. A token with no glob character is passed through completely
|
||||||
|
unchanged. A token that DOES contain a glob character but matches NO
|
||||||
|
real file is ALSO passed through completely unchanged, as its own
|
||||||
|
literal single token (bash's own default nullglob=off behavior) -- so a
|
||||||
|
program that tries to open a nonexistent glob gets the same "no such
|
||||||
|
file" error a real shell gives for an unmatched pattern, not a silently
|
||||||
|
vanished argument. Only the FINAL path component may contain glob syntax
|
||||||
|
(shellSplitGlobToken's own doc comment) -- `/tmp/gd/ *.txt` is supported,
|
||||||
|
`/tmp/ * /file.txt` is not. The directory part is resolved through
|
||||||
|
vfsResolvePath first, so a bare `*.txt` (no '/' at all) globs the
|
||||||
|
CALLING thread's own current directory, same cwd-awareness every other
|
||||||
|
path-taking builtin in this file already gets. Deliberately called by
|
||||||
|
runProgram/runPipelineStages AFTER shellExtractRedirects has already
|
||||||
|
pulled out redirection operators and their targets, and after the
|
||||||
|
trailing "&" background marker has already been stripped -- so a glob
|
||||||
|
character can never appear in a redirect target or be mistaken for
|
||||||
|
something other than a real program argument; an honestly-scoped
|
||||||
|
limitation matching this file's existing "no stage may be a builtin"/
|
||||||
|
redirect-target-scope documented choices elsewhere. Returns the new argc,
|
||||||
|
or -1 (argv left completely untouched) if expansion would need more than
|
||||||
|
`maxArgc` slots -- the same "malformed/too-large input, caller reports an
|
||||||
|
error instead of silently truncating" shape shellSplitPipelineStages
|
||||||
|
already uses for "too many stages". */
|
||||||
|
static int shellExpandGlobs(char** argv, int argc, int maxArgc, char storage[][shellGlobPathCapacity])
|
||||||
|
{
|
||||||
|
char* result[shellMaxArgs];
|
||||||
|
int resultCount = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < argc; i++) {
|
||||||
|
if (!shellTokenHasGlob(argv[i])) {
|
||||||
|
if (resultCount >= maxArgc)
|
||||||
|
return -1;
|
||||||
|
result[resultCount++] = argv[i];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
char dirPart[pathCapacity];
|
||||||
|
char patternPart[pathCapacity];
|
||||||
|
shellSplitGlobToken(argv[i], dirPart, sizeof(dirPart), patternPart, sizeof(patternPart));
|
||||||
|
|
||||||
|
char resolvedDir[pathCapacity];
|
||||||
|
vfsResolvePath(dirPart, resolvedDir, sizeof(resolvedDir));
|
||||||
|
|
||||||
|
int matchStart = resultCount;
|
||||||
|
VfsNode* dirNode = vfsLookup(resolvedDir);
|
||||||
|
if (dirNode && vfsIsDir(dirNode)) {
|
||||||
|
for (VfsNode* child = vfsChild(dirNode); child; child = vfsNext(child)) {
|
||||||
|
if (!shellGlobMatch(patternPart, vfsName(child)))
|
||||||
|
continue;
|
||||||
|
if (resultCount >= maxArgc)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
int pos = snprintf(storage[resultCount], shellGlobPathCapacity, "%s", resolvedDir);
|
||||||
|
if (pos < 0)
|
||||||
|
pos = 0;
|
||||||
|
if (pos >= shellGlobPathCapacity)
|
||||||
|
pos = shellGlobPathCapacity - 1;
|
||||||
|
snprintf(storage[resultCount] + pos, (uint64)(shellGlobPathCapacity - pos), "%s",
|
||||||
|
vfsName(child));
|
||||||
|
|
||||||
|
result[resultCount] = storage[resultCount];
|
||||||
|
resultCount++;
|
||||||
|
}
|
||||||
|
|
||||||
static void printHelp(void)
|
static void printHelp(void)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in new issue
Block a user