feat: extend shell with shellExtractRedirects and shellParseVarRef
This commit is contained in:
1 parent
96148ff0e7
commit
6bc12b43a7
1 file changed
+173
+173
@@ -192,6 +192,179 @@ int shellSplitAndOr(char* line, char* segments[], ShellAndOrOp andOrOps[], int m
|
|||||||
int opCount = 0;
|
int opCount = 0;
|
||||||
for (const char* cursor = line; *cursor; ) {
|
for (const char* cursor = line; *cursor; ) {
|
||||||
if ((cursor[0] == '&' && cursor[1] == '&') || (cursor[0] == '|' && cursor[1] == '|')) {
|
if ((cursor[0] == '&' && cursor[1] == '&') || (cursor[0] == '|' && cursor[1] == '|')) {
|
||||||
|
opCount++;
|
||||||
|
cursor += 2;
|
||||||
|
} else {
|
||||||
|
cursor++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opCount == 0)
|
||||||
|
return 0;
|
||||||
|
if (opCount + 1 > maxSegments)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
char* segStart = line;
|
||||||
|
char* cursor = line;
|
||||||
|
while (true) {
|
||||||
|
bool atAnd = (cursor[0] == '&' && cursor[1] == '&');
|
||||||
|
bool atOr = (cursor[0] == '|' && cursor[1] == '|');
|
||||||
|
bool atEnd = (*cursor == 0);
|
||||||
|
if (atAnd || atOr || atEnd) {
|
||||||
|
char* segEnd = cursor;
|
||||||
|
while (segEnd > segStart && segEnd[-1] == ' ')
|
||||||
|
segEnd--;
|
||||||
|
*segEnd = 0;
|
||||||
|
|
||||||
|
segments[count++] = segStart;
|
||||||
|
|
||||||
|
if (atEnd)
|
||||||
|
break;
|
||||||
|
|
||||||
|
andOrOps[count - 1] = atAnd ? shellAndOrAnd : shellAndOrOr;
|
||||||
|
|
||||||
|
cursor += 2;
|
||||||
|
segStart = cursor;
|
||||||
|
while (*segStart == ' ')
|
||||||
|
segStart++;
|
||||||
|
cursor = segStart;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cursor++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool shellExtractRedirects(char** argv, int* argc, ShellRedirect* out)
|
||||||
|
{
|
||||||
|
int count = *argc;
|
||||||
|
|
||||||
|
/* Validate BEFORE mutating anything: a dangling operator (no filename
|
||||||
|
token after it) must leave argv and argc completely untouched,
|
||||||
|
exactly like shellSplitPipelineStages leaves `line` untouched when it
|
||||||
|
finds no pipe -- the caller needs to tell "malformed, nothing
|
||||||
|
changed" apart from "changed, here is the result" without having to
|
||||||
|
snapshot argv itself first. */
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
bool isOperator = strcmp(argv[i], "<") == 0 || strcmp(argv[i], "<<<") == 0 ||
|
||||||
|
strcmp(argv[i], ">") == 0 || strcmp(argv[i], ">>") == 0 ||
|
||||||
|
strcmp(argv[i], "2>") == 0 || strcmp(argv[i], "2>>") == 0;
|
||||||
|
if (isOperator && i + 1 >= count)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
out->inPath = nullptr;
|
||||||
|
out->outPath = nullptr;
|
||||||
|
out->appendOut = false;
|
||||||
|
out->errPath = nullptr;
|
||||||
|
out->appendErr = false;
|
||||||
|
out->hereString = nullptr;
|
||||||
|
|
||||||
|
int writeIndex = 0;
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
if (strcmp(argv[i], "<") == 0) {
|
||||||
|
out->inPath = argv[++i];
|
||||||
|
out->hereString = nullptr;
|
||||||
|
} else if (strcmp(argv[i], "<<<") == 0) {
|
||||||
|
out->hereString = argv[++i];
|
||||||
|
out->inPath = nullptr;
|
||||||
|
} else if (strcmp(argv[i], ">") == 0) {
|
||||||
|
out->outPath = argv[++i];
|
||||||
|
out->appendOut = false;
|
||||||
|
} else if (strcmp(argv[i], ">>") == 0) {
|
||||||
|
out->outPath = argv[++i];
|
||||||
|
out->appendOut = true;
|
||||||
|
} else if (strcmp(argv[i], "2>") == 0) {
|
||||||
|
out->errPath = argv[++i];
|
||||||
|
out->appendErr = false;
|
||||||
|
} else if (strcmp(argv[i], "2>>") == 0) {
|
||||||
|
out->errPath = argv[++i];
|
||||||
|
out->appendErr = true;
|
||||||
|
} else {
|
||||||
|
argv[writeIndex++] = argv[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*argc = writeIndex;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool shellParseVarRef(const char* token, char* nameOut, int nameCap)
|
||||||
|
{
|
||||||
|
if (token[0] != '$' || token[1] == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int len = 0;
|
||||||
|
for (const char* c = token + 1; *c; c++) {
|
||||||
|
bool isNameChar = (*c >= 'a' && *c <= 'z') || (*c >= 'A' && *c <= 'Z') ||
|
||||||
|
(*c >= '0' && *c <= '9') || *c == '_';
|
||||||
|
if (!isNameChar)
|
||||||
|
return false;
|
||||||
|
len++;
|
||||||
|
}
|
||||||
|
if (len + 1 > nameCap)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
memcpy(nameOut, token + 1, (uint64)len);
|
||||||
|
nameOut[len] = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool shellIsStatusVarToken(const char* token)
|
||||||
|
{
|
||||||
|
return strcmp(token, "$?") == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Shared expansion rule behind both shellExpandArgv (per-argv-position, an
|
||||||
|
entire spawned program's argument list) and shellExpandRedirect
|
||||||
|
(per-redirect-target) below: `$?` (shellIsStatusVarToken, this shell's
|
||||||
|
own most-recently-completed status, lastStatus above) is checked FIRST
|
||||||
|
since shellParseVarRef can never match it anyway (see
|
||||||
|
shellIsStatusVarToken's own doc comment in shell.h), then a real
|
||||||
|
`$NAME` environment lookup (shellParseVarRef + currentThreadGetEnv, an
|
||||||
|
unset name expanding to an empty string like a real shell's own
|
||||||
|
default). Anything that is neither is returned completely untouched,
|
||||||
|
still pointing into whatever buffer the caller originally handed it --
|
||||||
|
`storage` is only ever written to, and only ever the return value, when
|
||||||
|
an expansion actually happened. */
|
||||||
|
static const char* shellExpandToken(const char* token, char* storage, int storageCap)
|
||||||
|
{
|
||||||
|
if (shellIsStatusVarToken(token)) {
|
||||||
|
snprintf(storage, (uint64)storageCap, "%ld", lastStatus);
|
||||||
|
return storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
char name[envEntryMax];
|
||||||
|
if (shellParseVarRef(token, name, sizeof(name))) {
|
||||||
|
const char* value = currentThreadGetEnv(name);
|
||||||
|
snprintf(storage, (uint64)storageCap, "%s", value ? value : "");
|
||||||
|
return storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Resolves every `$?`/`$NAME` token in argv[0..argc) via shellExpandToken
|
||||||
|
above, overwriting argv[i] with a pointer into storage[i] whenever one
|
||||||
|
expands -- necessary because argv[i] itself is a pointer into the
|
||||||
|
shared line buffer with only enough room for the ORIGINAL (unexpanded)
|
||||||
|
token, never enough for whatever length the variable's value happens to
|
||||||
|
be. */
|
||||||
|
static void shellExpandArgv(char** argv, int argc, char storage[][envEntryMax])
|
||||||
|
{
|
||||||
|
for (int i = 0; i < argc; i++)
|
||||||
|
argv[i] = (char*)shellExpandToken(argv[i], storage[i], envEntryMax);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Expands `$?`/`$NAME` (shellExpandToken above) in each of ShellRedirect's
|
||||||
|
own target fields (inPath/outPath/errPath/hereString) in place -- these
|
||||||
|
come straight from shellExtractRedirects, which only ever pulls raw
|
||||||
|
tokens out of argv with no expansion of its own, so a redirect target or
|
||||||
|
here-string text written as `$NAME`/`$?` was previously left completely
|
||||||
|
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
|
||||||
|
|
||||||
static void printHelp(void)
|
static void printHelp(void)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in new issue
Block a user