//----------------------------------------------------------------------------// // GNU GPL OS/K // // // // Authors: spectral` // // NeoX // // // // Desc: Command line parsing utilities // //----------------------------------------------------------------------------// #include #include #include #include // // Computes argument count, the least N such // that argv[N] == NULL // int KalComputeArgCount(const char **argv) { int argc = 0; while (argv[argc]) argc++; return argc; } // // Computes the total size of argv, including // the null-terminators // size_t KalComputeArgVecSize(const char *argv[]) { size_t len; for (len = 0; *argv; len += strlen(*argv) + 1, argv++); return len; } // // Converts command line to an argument vector // // This function assumes that argv[0] is a pointer // to a ARG_MAX-wide buffer, which will be filled // with strings in succession; the address of the nth // string will be stored in argv[n-1] // // Technically ARG_MAX is the maximum number of bytes // in both the buffer *and* argv, i.e. (argc + 1) * sizeof(char *) // bytes are reserved for the argv[i] pointers, so in fact less than // ARG_MAX bytes are available // // That available space, however, remains strictly higher than 4KB, // which is the POSIX minimum; in case of doubt, safely use 4KB // // TODO long escape sequences // get program command line if cmdLine == NULL // error_t KalCmdLineToArgVecEx(const char *cmdLine, int *argcPtr, char **argv, bool doEscaping) { int argc; char quotes = 0; bool started = false; bool escaping = false; size_t written = 0; error_t retcode = EOK; KalAlwaysAssert(argv && *argv && cmdLine); // An ARG_MAX-wide buffer char *buffer = *argv; // 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++ + 1; \ 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 (argc = 0; *cmdLine; cmdLine++) { // Make sure we don't go beyond ARG_MAX bytes if (written >= ARG_MAX - (1 + sizeof(char *))) { // All we have left is one byte for the null-terminator of the current slot // and sizeof(char *) bytes for the NULL at the end of argv *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 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 KalCmdLineToArgVec(const char *cmdLine, int *argcPtr, char **argv) { return KalCmdLineToArgVecEx(cmdLine, argcPtr, argv, false); }