void MAKEFLAGS_break(ArgArray *aa, const char str[]) { char *arg; char *start; ArgArray_Init(aa); aa->buffer = strdup(str); arg = aa->buffer; start = NULL; for (;;) { switch (str[0]) { case ' ': case '\t': /* word separator */ if (start == NULL) { /* not in a word */ str++; continue; } /* FALLTHRU */ case '\0': if (aa->argc == aa->size) { aa->size *= 2; aa->argv = erealloc(aa->argv, (aa->size + 1) * sizeof(char *)); } *arg++ = '\0'; if (start == NULL) { aa->argv[aa->argc] = start; return; } if (str[0] == '\0') { aa->argv[aa->argc++] = start; aa->argv[aa->argc] = NULL; return; } else { aa->argv[aa->argc++] = start; start = NULL; str++; continue; } case '\\': if (str[1] == ' ' || str[1] == '\t') str++; break; default: break; } if (start == NULL) start = arg; *arg++ = *str++; } }
/** * Fracture a string into an array of words (as delineated by tabs or * spaces) taking quotation marks into account. Leading tabs/spaces * are ignored. */ void brk_string(ArgArray *aa, const char str[], Boolean expand) { char inquote; char *start; char *arg; /* skip leading space chars. */ for (; *str == ' ' || *str == '\t'; ++str) continue; ArgArray_Init(aa); aa->buffer = estrdup(str); arg = aa->buffer; start = arg; inquote = '\0'; /* * copy the string; at the same time, parse backslashes, * quotes and build the argument list. */ for (;;) { switch (str[0]) { case '"': case '\'': if (inquote == '\0') { inquote = str[0]; if (expand) break; if (start == NULL) start = arg; } else if (inquote == str[0]) { inquote = '\0'; /* Don't miss "" or '' */ if (start == NULL) start = arg; if (expand) break; } else { /* other type of quote found */ if (start == NULL) start = arg; } *arg++ = str[0]; break; case ' ': case '\t': case '\n': if (inquote) { if (start == NULL) start = arg; *arg++ = str[0]; break; } if (start == NULL) break; /* FALLTHROUGH */ case '\0': /* * end of a token -- make sure there's enough argv * space and save off a pointer. */ if (aa->argc == aa->size) { aa->size *= 2; /* ramp up fast */ aa->argv = erealloc(aa->argv, (aa->size + 1) * sizeof(char *)); } *arg++ = '\0'; if (start == NULL) { aa->argv[aa->argc] = start; return; } if (str[0] == '\n' || str[0] == '\0') { aa->argv[aa->argc++] = start; aa->argv[aa->argc] = NULL; return; } else { aa->argv[aa->argc++] = start; start = NULL; break; } case '\\': if (start == NULL) start = arg; if (expand) { switch (str[1]) { case '\0': case '\n': /* hmmm; fix it up as best we can */ *arg++ = '\\'; break; case 'b': *arg++ = '\b'; ++str; break; case 'f': *arg++ = '\f'; ++str; break; case 'n': *arg++ = '\n'; ++str; break; case 'r': *arg++ = '\r'; ++str; break; case 't': *arg++ = '\t'; ++str; break; default: *arg++ = str[1]; ++str; break; } } else { *arg++ = str[0]; if (str[1] != '\0') { ++str; *arg++ = str[0]; } } break; default: if (start == NULL) start = arg; *arg++ = str[0]; break; } ++str; } }
/** * Parse a shell specification line and return the new Shell structure. * In case of an error a message is printed and NULL is returned. * * Notes: * A shell specification consists of a .SHELL target, with dependency * operator, followed by a series of blank-separated words. Double * quotes can be used to use blanks in words. A backslash escapes * anything (most notably a double-quote and a space) and * provides the functionality it does in C. Each word consists of * keyword and value separated by an equal sign. There should be no * unnecessary spaces in the word. The keywords are as follows: * name Name of shell. * path Location of shell. Overrides "name" if given * quiet Command to turn off echoing. * echo Command to turn echoing on * filter Result of turning off echoing that shouldn't be * printed. * echoFlag Flag to turn echoing on at the start * errFlag Flag to turn error checking on at the start * hasErrCtl True if shell has error checking control * check Command to turn on error checking if hasErrCtl * is true or template of command to echo a command * for which error checking is off if hasErrCtl is * false. * ignore Command to turn off error checking if hasErrCtl * is true or template of command to execute a * command so as to ignore any errors it returns if * hasErrCtl is false. * builtins A space separated list of builtins. If one * of these builtins is detected when make wants * to execute a command line, the command line is * handed to the shell. Otherwise make may try to * execute the command directly. If this list is empty * it is assumed, that the command must always be * handed over to the shell. * meta The shell meta characters. If this is not specified * or empty, commands are alway passed to the shell. * Otherwise they are not passed when they contain * neither a meta character nor a builtin command. */ static Shell * ShellParseSpec(const char spec[], bool *fullSpec) { ArgArray aa; Shell *sh; char *eq; char *keyw; int arg; *fullSpec = false; sh = emalloc(sizeof(*sh)); memset(sh, 0, sizeof(*sh)); ArgArray_Init(&sh->builtins); /* * Parse the specification by keyword but skip the first word */ brk_string(&aa, spec, true); for (arg = 1; arg < aa.argc; arg++) { /* * Split keyword and value */ keyw = aa.argv[arg]; if ((eq = strchr(keyw, '=')) == NULL) { Parse_Error(PARSE_FATAL, "missing '=' in shell " "specification keyword '%s'", keyw); ArgArray_Done(&aa); Shell_Destroy(sh); return (NULL); } *eq++ = '\0'; if (strcmp(keyw, "path") == 0) { free(sh->path); sh->path = estrdup(eq); } else if (strcmp(keyw, "name") == 0) { free(sh->name); sh->name = estrdup(eq); } else if (strcmp(keyw, "quiet") == 0) { free(sh->echoOff); sh->echoOff = estrdup(eq); *fullSpec = true; } else if (strcmp(keyw, "echo") == 0) { free(sh->echoOn); sh->echoOn = estrdup(eq); *fullSpec = true; } else if (strcmp(keyw, "filter") == 0) { free(sh->noPrint); sh->noPrint = estrdup(eq); *fullSpec = true; } else if (strcmp(keyw, "echoFlag") == 0) { free(sh->echo); sh->echo = estrdup(eq); *fullSpec = true; } else if (strcmp(keyw, "errFlag") == 0) { free(sh->exit); sh->exit = estrdup(eq); *fullSpec = true; } else if (strcmp(keyw, "hasErrCtl") == 0) { sh->hasErrCtl = ( *eq == 'Y' || *eq == 'y' || *eq == 'T' || *eq == 't'); *fullSpec = true; } else if (strcmp(keyw, "check") == 0) { free(sh->errCheck); sh->errCheck = estrdup(eq); *fullSpec = true; } else if (strcmp(keyw, "ignore") == 0) { free(sh->ignErr); sh->ignErr = estrdup(eq); *fullSpec = true; } else if (strcmp(keyw, "builtins") == 0) { ArgArray_Done(&sh->builtins); brk_string(&sh->builtins, eq, true); qsort(sh->builtins.argv + 1, sh->builtins.argc - 1, sizeof(char *), sort_builtins); *fullSpec = true; } else if (strcmp(keyw, "meta") == 0) { free(sh->meta); sh->meta = estrdup(eq); *fullSpec = true; } else if (strcmp(keyw, "unsetenv") == 0) { sh->unsetenv = ( *eq == 'Y' || *eq == 'y' || *eq == 'T' || *eq == 't'); *fullSpec = true; } else { Parse_Error(PARSE_FATAL, "unknown keyword in shell " "specification '%s'", keyw); ArgArray_Done(&aa); Shell_Destroy(sh); return (NULL); } } ArgArray_Done(&aa); /* * Some checks (could be more) */ if (*fullSpec) { if ((sh->echoOn != NULL) ^ (sh->echoOff != NULL)) { Parse_Error(PARSE_FATAL, "Shell must have either both " "echoOff and echoOn or none of them"); Shell_Destroy(sh); return (NULL); } if (sh->echoOn != NULL && sh->echoOff != NULL) sh->hasEchoCtl = true; } return (sh); }