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
|
||||
already covered) -- an honestly-scoped gap flagged when `$NAME`
|
||||
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)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user