+
+// cheapsh.c
+
+/*
+
+License: BSD0
+
+Permission to use, copy, modify, and/or distribute this software for
+any purpose with or without fee is hereby granted.
+
+THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL
+WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE
+FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
+DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+*/
+
+#include <ctype.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/wait.h>
+
+struct buffer {
+ size_t len, allo;
+ char *data;
+};
+
+enum status {
+ STATUS_ERROR = -1,
+ STATUS_OK = 0,
+ STATUS_END = '\n',
+ STATUS_SPACE = ' ',
+ STATUS_BACKSLASH = '\\',
+};
+
+#define BUFFER_RESIZE(b, f) do { \
+ void *np; \
+ buf->len += buf->len == 0; \
+ if ((b)->len + 1 < (b)->allo) \
+ break; \
+ (b)->allo = (b)->data != NULL ? (b)->allo * 2 : 8; \
+ np = realloc((b)->data, (b)->allo * (f)); \
+ if (np == NULL) { \
+ perror("realloc"); \
+ return STATUS_ERROR; \
+ } \
+ (b)->data = np; \
+} while (0)
+
+static inline enum status buffer_append_char(struct buffer *buf, char c) {
+ BUFFER_RESIZE(buf, 1);
+ buf->data[buf->len - 1] = c;
+ buf->data[buf->len++] = '\0';
+ return STATUS_OK;
+}
+
+#define CHARPP(c) ((char**)(c))
+
+static inline enum status buffer_append_char_ptr(struct buffer *buf, char *ptr) {
+ BUFFER_RESIZE(buf, sizeof ptr);
+ CHARPP(buf->data)[buf->len - 1] = ptr;
+ CHARPP(buf->data)[buf->len++] = NULL;
+ return STATUS_OK;
+}
+
+static inline enum status push_arg(
+ struct buffer *strs, struct buffer *ptrs, char *start
+) {
+ if (buffer_append_char_ptr(ptrs, start) == STATUS_ERROR)
+ return STATUS_ERROR;
+ return buffer_append_char(strs, '\0');
+}
+
+static inline enum status wordsplit_unquoted_char(int c) {
+ switch (c) {
+ case ' ':
+ case '\t':
+ return STATUS_SPACE;
+ case '\\':
+ return STATUS_BACKSLASH;
+ case '\r':
+ case '\n':
+ case ';':
+ return STATUS_END;
+ }
+ return STATUS_OK;
+}
+
+enum status wordsplit(FILE *fh, struct buffer *strs, struct buffer *ptrs) {
+ uintptr_t start = 0;
+ size_t count = 0, i;
+ int c;
+ enum status st = STATUS_OK;
+ while ((c = fgetc(fh)) != EOF) {
+ if (st == STATUS_BACKSLASH) {
+ if ((st = buffer_append_char(strs, c)) == STATUS_ERROR)
+ return STATUS_ERROR;
+ continue;
+ }
+ switch (st = wordsplit_unquoted_char(c)) {
+ case STATUS_ERROR:
+ return STATUS_ERROR;
+ case STATUS_OK:
+ if (!isprint(c)) {
+ fprintf(stderr, "Error: invalid input: '%d'\n", c);
+ return STATUS_ERROR;
+ } else if (buffer_append_char(strs, c) == STATUS_ERROR)
+ return STATUS_ERROR;
+ break;
+ case STATUS_END:
+ goto done;
+ case STATUS_SPACE:
+ if (strs->len == start)
+ return STATUS_OK;
+ else if (push_arg(strs, ptrs, (char*)start) == STATUS_ERROR)
+ return STATUS_ERROR;
+ start = strs->len - 1;
+ count++;
+ break;
+ case STATUS_BACKSLASH:
+ break;
+ }
+ }
+done:
+ if (c == EOF && ferror(fh) != 0) {
+ fprintf(stderr, "Error: read error!\n");
+ return STATUS_ERROR;
+ } else if (st == STATUS_BACKSLASH) {
+ fprintf(stderr, "Error: backslash at EOF!\n");
+ return STATUS_ERROR;
+ } else if (strs->len > start) {
+ if (push_arg(strs, ptrs, (char*)start) == STATUS_ERROR)
+ return STATUS_ERROR;
+ count++;
+ start = strs->len;
+ }
+ for (i = 0; i < count; i++)
+ CHARPP(ptrs->data)[i] += (uintptr_t)strs->data;
+ return c == EOF ? STATUS_END : STATUS_OK;
+}
+
+static inline enum status execute(char **argv) {
+ pid_t pid = fork();
+ if (pid == 0) {
+ execvp(argv[0], argv);
+ perror("execvp");
+ return STATUS_ERROR;
+ } else if (waitpid(pid, NULL, 0) < 0) {
+ perror("waitpid");
+ return STATUS_ERROR;
+ }
+ return STATUS_OK;
+}
+
+int main(int argc, char *argv[]) {
+ int ret = EXIT_FAILURE;
+ enum status st;
+ struct buffer strs = { 0, 0, NULL }, ptrs = { 0, 0, NULL };
+ do {
+ if (
+ (st = wordsplit(stdin, &strs, &ptrs)) == STATUS_ERROR
+ || (ptrs.len > 0 && execute(CHARPP(ptrs.data)) == STATUS_ERROR)
+ )
+ goto error;
+ ptrs.len = strs.len = 0;
+ } while (st != STATUS_END);
+ ret = EXIT_SUCCESS;
+error:
+ free(strs.data);
+ free(ptrs.data);
+ return ret;
+ (void)argc;
+ (void)argv;
+}