From: mar77i Date: Wed, 4 Mar 2026 15:44:25 +0000 (+0100) Subject: initial commit X-Git-Url: https://git.mar77i.info/?a=commitdiff_plain;h=HEAD;p=cheapsh initial commit --- 99b8a377038d875e8ca428edc56eaa40f826bdec diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0fcbd19 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +cheapsh +cheapsh.o diff --git a/Makefile b/Makefile new file mode 100644 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 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 +#include +#include +#include +#include +#include +#include + +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 index 0000000..92175d8 --- /dev/null +++ b/global.mk @@ -0,0 +1,35 @@ + +# global.mk +# +# Copyright (c) 2017, mar77i +# +# 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