os-k/kaleid/kernel/sh/argv.c

191 lines
6.3 KiB
C

//----------------------------------------------------------------------------//
// OS on Kaleid //
// //
// Desc: Command line parsing utilities //
// //
// //
// Copyright © 2018-2020 The OS/K Team //
// //
// This file is part of OS/K. //
// //
// OS/K is free software: you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation, either version 3 of the License, or //
// any later version. //
// //
// OS/K is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY//without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with OS/K. If not, see <https://www.gnu.org/licenses/>. //
//----------------------------------------------------------------------------//
#include <sh/argv.h>
#include <ex/malloc.h>
//
// Converts command line to an argument vector
//
// This function assumes that *argv is a (ARG_MAX * 2)-wide buffer,
// whose second half will be filled with strings in succession;
// with the address of the nth string stored in the first half,
// specifically at argv[n-1]
//
error_t ShCmdLineToArgVecEx(const char *cmdLine,
int *argcPtr,
char *bufptr,
bool doEscaping)
{
int argc = 0;
char quotes = 0;
bool started = false;
bool escaping = false;
size_t written = 0;
error_t retcode = EOK;
bool maxreached = false;
// An ARG_MAX-wide buffer
char **argv = (char **)bufptr;
assert(argv && cmdLine);
// Another ARG_MAX-wide buffer
char *buffer = bufptr + ARG_MAX;
argv[0] = buffer;
// Null-terminate current argv slot
// and save the start of next string
// Macro'd to avoid copypasting code
#define NULLTERM_AND_SAVE \
*buffer = 0; \
argv[++argc] = ++buffer; \
written += sizeof(char *) + 1; \
// Is character a blank character?
// To be replaced by ctype stuff once
// that's implemented
#define ISBLANK(c) ((c) == ' ' || (c) == '\t' || (c) == '\n')
// Both " and ' are valid quotes chars
// XXX aren't there more?
#define ISQUOTE(c) ((c) == '\'' || (c) == '"')
// Go through the command line
for (; *cmdLine; cmdLine++) {
// Make sure we don't go beyond ARG_MAX bytes
maxreached = written >= ARG_MAX - 1;
maxreached = maxreached || argc >= (int)(ARG_MAX/sizeof(char *) - 1);
if (maxreached) {
// Sanity check
assert(written == ARG_MAX - 1);
// All we have left is one byte for the null-terminator
// (or argv maxed out)
*buffer = 0;
// Did we write anything in this slot?
if (started) {
argc++;
}
// We're done, get out of here
retcode = ENOMEM;
break;
}
// Switch to next argv slot
if (ISBLANK(*cmdLine) && !quotes && !escaping) {
// Has slot even started?
if (started) {
started = false;
NULLTERM_AND_SAVE;
}
continue;
}
// Escaping next character
if (*cmdLine == '\\' && !escaping && doEscaping) {
escaping = true;
continue;
}
// Deal with escape sequences
if (escaping) {
if (*cmdLine == 'n') *buffer++ = '\n';
else if (*cmdLine == 'r') *buffer++ = '\r';
else if (*cmdLine == 't') *buffer++ = '\t';
else if (*cmdLine == 'f') *buffer++ = '\f';
else if (*cmdLine == 'v') *buffer++ = '\v';
written++;
started = true;
escaping = false;
continue;
}
// Deal with quotes
if (ISQUOTE(*cmdLine) && !escaping) {
// Quoted text always fills a whole slot
// Note that this is the only way an empty
// string can be put in a slot
if (!quotes && !started) {
quotes = *cmdLine;
started = true;
continue;
}
// End a quote block
if (quotes == *cmdLine && ISBLANK(cmdLine[1])) {
quotes = 0;
started = false;
NULLTERM_AND_SAVE;
continue;
}
// Quotes were either preceeded by unquoted non-blank text
// or couldn't close quoted text block because succeeded
// by text; we consider this " to be escaped and fall through
}
// Just a regular character, or it is being escaped
written++;
started = true;
escaping = false;
*buffer++ = *cmdLine;
}
// Ensures that the last string is null-terminated
*buffer = 0;
// Ensures that argv[argc] == NULL
argv[++argc] = NULL;
// Update *argcPtr, but we don't mind if
// NULL was passed for argcPtr
if (argcPtr) {
*argcPtr = argc;
}
return retcode;
}
#undef ISQUOTE
#undef ISBLANK
#undef NULLTERM_AND_SAVE
error_t ShCmdLineToArgVec(const char *cmdLine,
int *argcPtr,
char *bufptr)
{
return ShCmdLineToArgVecEx(cmdLine, argcPtr, bufptr, false);
}