]> git.mar77i.info Git - cheapsh/commitdiff
initial commit master
authormar77i <mar77i@protonmail.ch>
Wed, 4 Mar 2026 15:44:25 +0000 (16:44 +0100)
committermar77i <mar77i@protonmail.ch>
Wed, 4 Mar 2026 15:44:25 +0000 (16:44 +0100)
.gitignore [new file with mode: 0644]
Makefile [new file with mode: 0644]
cheapsh.c [new file with mode: 0644]
global.mk [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..0fcbd19
--- /dev/null
@@ -0,0 +1,2 @@
+cheapsh
+cheapsh.o
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..78029e1
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,12 @@
+
+CFLAGS += -D_DEFAULT_SOURCE $(DEV_CFLAGS)
+LDFLAGS += $(DEV_LDFLAGS)
+# LDLIBS +=
+
+cheapsh: cheapsh.o
+
+all: cheapsh
+
+include global.mk
+
+.PHONY: all
diff --git a/cheapsh.c b/cheapsh.c
new file mode 100644 (file)
index 0000000..0969877
--- /dev/null
+++ b/cheapsh.c
@@ -0,0 +1,180 @@
+
+// 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;
+}
diff --git a/global.mk b/global.mk
new file mode 100644 (file)
index 0000000..92175d8
--- /dev/null
+++ b/global.mk
@@ -0,0 +1,35 @@
+
+# global.mk
+#
+# Copyright (c) 2017, mar77i <mar77i at protonmail dot ch>
+#
+# This software may be modified and distributed under the terms
+# of the ISC license.  See the LICENSE file for details.
+
+# useful compiler flags; thanks xavier roche @ httrack blog
+
+CFLAGS += -std=c99 -pipe -fvisibility=hidden -Wall -Wextra -Wformat \
+       -Wformat-security -Wreturn-type -Wpointer-arith -Winit-self \
+       -Wsign-compare -Wmultichar -Wuninitialized -Werror -funroll-loops \
+       -funswitch-loops -pedantic
+
+SHARED_CFLAGS = -fPIC -fvisibility=default
+
+LDFLAGS += -Wl,-z,relro,-z,now,-O1,--as-needed,--no-undefined \
+       -Wl,--build-id=sha1,--no-allow-shlib-undefined -rdynamic
+
+VALGRIND_FLAGS = --trace-children=yes --leak-check=full --track-origins=yes \
+       --show-leak-kinds=all
+
+LINK = $(CC) $(LDFLAGS) $> $^ $(LOADLIBES) $(LDLIBS) $(LDADD) -o $@
+LINK_SHARED = \
+       $(CC) $(LDFLAGS) $> $^ $(LOADLIBES) $(LDLIBS) $(LDADD) -shared -o $@
+
+vg:
+       valgrind $(VALGRIND_FLAGS) $(ARGS)
+
+DEV_CFLAGS = -Og -g
+PROD_CFLAGS = -O3
+PROD_LDFLAGS = -Wl,--discard-all
+
+.PHONY: vg