diff --git a/lcthw-remnants-2/.gitignore b/lcthw-remnants-2/.gitignore index 6f17809..81aa9e6 100644 --- a/lcthw-remnants-2/.gitignore +++ b/lcthw-remnants-2/.gitignore @@ -1,3 +1,4 @@ *.dat ex* !ex*.c +devpkgzed diff --git a/lcthw-remnants-2/Dockerfile b/lcthw-remnants-2/Dockerfile index 32e1b8b..14960d9 100644 --- a/lcthw-remnants-2/Dockerfile +++ b/lcthw-remnants-2/Dockerfile @@ -3,4 +3,4 @@ FROM ubuntu:xenial ENV DEBIAN_FRONTEND noninteractive RUN apt-get update -yq -RUN apt-get install -yq build-essential git man curl file +RUN apt-get install -yq build-essential git man curl file sudo vim-tiny diff --git a/lcthw-remnants-2/devpkg/.gitignore b/lcthw-remnants-2/devpkg/.gitignore new file mode 100644 index 0000000..e8e5ca9 --- /dev/null +++ b/lcthw-remnants-2/devpkg/.gitignore @@ -0,0 +1,2 @@ +bstrlib.* +devpkg diff --git a/lcthw-remnants-2/devpkg/Makefile b/lcthw-remnants-2/devpkg/Makefile new file mode 100644 index 0000000..40cf514 --- /dev/null +++ b/lcthw-remnants-2/devpkg/Makefile @@ -0,0 +1,30 @@ +BSTRLIB_BASE_URL ?= https://raw.githubusercontent.com/websnarf/bstrlib/208b1f2a4dfc96b806ed499bd1909e87ec15981d +PREFIX ?= /usr/local +CFLAGS = -g -Wall -I${PREFIX}/apr/include/apr-1 -I${PREFIX}/apr/include/apr-util-1 +LDFLAGS = -L${PREFIX}/apr/lib +LDLIBS = -lapr-1 -pthread -laprutil-1 + +all: devpkg + +devpkg: bstrlib.o db.o shell.o commands.o + +prebuild: + sudo mkdir -p /etc/ld.so.conf.d + echo $(DESTDIR)/$(PREFIX)/lib | sudo tee /etc/ld.so.conf.d/devpkg.conf + sudo ldconfig + +install: all + install -d $(DESTDIR)/$(PREFIX)/bin/ + install devpkg $(DESTDIR)/$(PREFIX)/bin/ + +clean: + $(RM) *.o + $(RM) devpkg + $(RM) *.dSYM + $(RM) bstrlib.c bstrlib.h + +bstrlib.c: bstrlib.h + curl -sSLO $(BSTRLIB_BASE_URL)/bstrlib.c + +bstrlib.h: + curl -sSLO $(BSTRLIB_BASE_URL)/bstrlib.h diff --git a/lcthw-remnants-2/devpkg/README b/lcthw-remnants-2/devpkg/README new file mode 100644 index 0000000..4e973c9 --- /dev/null +++ b/lcthw-remnants-2/devpkg/README @@ -0,0 +1,47 @@ +# `devpkg` + +This is a thingy that downloads, builds, and installs stuff! + +## usage + +Before you can do the fun part, you need to initialize the bits: + +``` bash +devpkg -S +``` + +Install something via URL: + +``` bash +devpkg -I http://example.org/foo.tar.bz2 +``` + +Install something via URL with custom arguments for configure, make, and +install: + +``` bash +devpkg -I http://example.org/foo.tar.bz2 -c '--no-flair' -m 'CFLAGS+=-Wall' -i '-n' +``` + +Perform a fetch *only* without building or installing: + +``` bash +devpkg -F http://example.org/foo.tar.bz2 +``` + +Perform a build *only* without installing: + +``` bash +devpkg -B http://example.org/foo.tar.bz2 +``` + +List the stuff that's been installed: + +``` bash +devpkg -L +``` + + + diff --git a/lcthw-remnants-2/devpkg/commands.c b/lcthw-remnants-2/devpkg/commands.c new file mode 100644 index 0000000..5c82735 --- /dev/null +++ b/lcthw-remnants-2/devpkg/commands.c @@ -0,0 +1,174 @@ +#include +#include +#include + +#include "commands.h" +#include "dbg.h" +#include "bstrlib.h" +#include "db.h" +#include "shell.h" + + +int Command_depends(apr_pool_t *p, const char *path) +{ + FILE *in = NULL; + bstring line = NULL; + + in = fopen(path, "r"); + check(in != NULL, "Failed to open downloaded depends: %s", path); + + for(line = bgets((bNgetc)fgetc, in, '\n'); line != NULL; + line = bgets((bNgetc)fgetc, in, '\n')) + { + btrimws(line); + log_info("Processing depends: %s", bdata(line)); + int rc = Command_install(p, bdata(line), NULL, NULL, NULL); + check(rc == 0, "Failed to install: %s", bdata(line)); + bdestroy(line); + } + + fclose(in); + return 0; + +error: + if(line) bdestroy(line); + if(in) fclose(in); + return -1; +} + +int Command_fetch(apr_pool_t *p, const char *url, int fetch_only) +{ + apr_uri_t info = {.port = 0}; + int rc = 0; + const char *depends_file = NULL; + apr_status_t rv = apr_uri_parse(p, url, &info); + + check(rv == APR_SUCCESS, "Failed to parse URL: %s", url); + + if(apr_fnmatch(GIT_PAT, info.path, 0) == APR_SUCCESS) { + rc = Shell_exec(GIT_SH, "URL", url, NULL); + check(rc == 0, "git failed."); + } else if(apr_fnmatch(DEPEND_PAT, info.path, 0) == APR_SUCCESS) { + check(!fetch_only, "No point in fetching a DEPENDS file."); + + if(info.scheme) { + depends_file = DEPENDS_PATH; + rc = Shell_exec(CURL_SH, "URL", url, "TARGET", depends_file, NULL); + check(rc == 0, "Curl failed."); + } else { + depends_file = info.path; + } + + // recursively process the devpkg list + log_info("Building according to DEPENDS: %s", url); + rv = Command_depends(p, depends_file); + check(rv == 0, "Failed to process the DEPENDS: %s", url); + + // this indicates that nothing needs to be done + return 0; + + } else if(apr_fnmatch(TAR_GZ_PAT, info.path, 0) == APR_SUCCESS) { + if(info.scheme) { + rc = Shell_exec(CURL_SH, + "URL", url, + "TARGET", TAR_GZ_SRC, NULL); + check(rc == 0, "Failed to curl source: %s", url); + } + + rv = apr_dir_make_recursive(BUILD_DIR, + APR_UREAD | APR_UWRITE | APR_UEXECUTE, p); + check(rv == APR_SUCCESS, "Failed to make directory %s", BUILD_DIR); + + rc = Shell_exec(TAR_SH, "FILE", TAR_GZ_SRC, NULL); + check(rc == 0, "Failed to untar %s", TAR_GZ_SRC); + } else if(apr_fnmatch(TAR_BZ2_PAT, info.path, 0) == APR_SUCCESS) { + if(info.scheme) { + rc = Shell_exec(CURL_SH, "URL", url, "TARGET", TAR_BZ2_SRC, NULL); + check(rc == 0, "Curl failed."); + } + + apr_status_t rc = apr_dir_make_recursive(BUILD_DIR, + APR_UREAD | APR_UWRITE | APR_UEXECUTE, p); + + check(rc == 0, "Failed to make directory %s", BUILD_DIR); + rc = Shell_exec(TAR_SH, "FILE", TAR_BZ2_SRC, NULL); + check(rc == 0, "Failed to untar %s", TAR_BZ2_SRC); + } else { + sentinel("Don't know how to handle %s", url); + } + + // indicates that an install needs to actually run + return 1; +error: + return -1; +} + +int Command_build(apr_pool_t *p, const char *url, const char *configure_opts, + const char *make_opts, const char *install_opts) +{ + int rc = 0; + + check(access(BUILD_DIR, X_OK | R_OK | W_OK) == 0, + "Build directory doesn't exist: %s", BUILD_DIR); + + // actually do an install + if(access(CONFIG_SCRIPT, X_OK) == 0) { + log_info("Has a configure script, running it."); + rc = Shell_exec(CONFIGURE_SH, "OPTS", configure_opts, NULL); + check(rc == 0, "Failed to configure"); + } + + rc = Shell_exec(MAKE_SH, "OPTS", make_opts, NULL); + check(rc == 0, "Failed to build."); + + rc = Shell_exec(INSTALL_SH, + "TARGET", install_opts ? install_opts : "install", + NULL); + check(rc == 0, "Failed to install."); + + rc = Shell_exec(CLEANUP_SH, NULL); + check(rc == 0, "Failed to cleanup after build."); + + rc = DB_update(url); + check(rc == 0, "Failed to add this package to the database."); + + return 0; + +error: + return -1; +} + +int Command_install(apr_pool_t *p, const char *url, const char *configure_opts, + const char *make_opts, const char *install_opts) +{ + int rc = 0; + check(Shell_exec(CLEANUP_SH, NULL) == 0, "Failed to cleanup before building."); + + rc = DB_find(url); + check(rc != -1, "Error checking the install database."); + + if(rc == 1) { + log_info("Package %s already installed.", url); + return 0; + } + + rc = Command_fetch(p, url, 0); + + if(rc == 1) { + rc = Command_build(p, url, configure_opts, make_opts, install_opts); + check(rc == 0, "Failed to build: %s", url); + } else if(rc == 0) { + // no install needed + log_info("Depends successfully installed: %s", url); + } else { + // had an error + sentinel("Install failed: %s", url); + } + + Shell_exec(CLEANUP_SH, NULL); + return 0; + +error: + Shell_exec(CLEANUP_SH, NULL); + return -1; +} diff --git a/lcthw-remnants-2/devpkg/commands.h b/lcthw-remnants-2/devpkg/commands.h new file mode 100644 index 0000000..aabe6a4 --- /dev/null +++ b/lcthw-remnants-2/devpkg/commands.h @@ -0,0 +1,31 @@ +#ifndef _commands_h +#define _commands_h + +#include + +#define DEPENDS_PATH "/tmp/DEPENDS" +#define TAR_GZ_SRC "/tmp/pkg-src.tar.gz" +#define TAR_BZ2_SRC "/tmp/pkg-src.tar.bz2" +#define BUILD_DIR "/tmp/pkg-build" +#define GIT_PAT "*.git" +#define DEPEND_PAT "*DEPENDS" +#define TAR_GZ_PAT "*.tar.gz" +#define TAR_BZ2_PAT "*.tar.bz2" +#define CONFIG_SCRIPT "/tmp/pkg-build/configure" + +enum CommandType { + COMMAND_NONE, COMMAND_INSTALL, COMMAND_LIST, COMMAND_FETCH, + COMMAND_INIT, COMMAND_BUILD +}; + +int Command_fetch(apr_pool_t *p, const char *url, int fetch_only); + +int Command_install(apr_pool_t *p, const char *url, const char *configure_opts, + const char *make_opts, const char *install_opts); + +int Command_depends(apr_pool_t *p, const char *path); + +int Command_build(apr_pool_t *p, const char *url, const char *configure_opts, + const char *make_opts, const char *install_opts); + +#endif diff --git a/lcthw-remnants-2/devpkg/db.c b/lcthw-remnants-2/devpkg/db.c new file mode 100644 index 0000000..879e55d --- /dev/null +++ b/lcthw-remnants-2/devpkg/db.c @@ -0,0 +1,125 @@ +#include +#include +#include + +#include "db.h" +#include "bstrlib.h" +#include "dbg.h" + +static FILE *DB_open(const char *path, const char *mode) +{ + return fopen(path, mode); +} + + +static void DB_close(FILE *db) +{ + fclose(db); +} + + +static bstring DB_load() +{ + FILE *db = NULL; + bstring data = NULL; + + db = DB_open(DB_FILE, "r"); + check(db, "Failed to open database: %s", DB_FILE); + + data = bread((bNread)fread, db); + check(data, "Failed to read from db file: %s", DB_FILE); + + DB_close(db); + return data; + +error: + if(db) DB_close(db); + if(data) bdestroy(data); + return NULL; +} + + +int DB_update(const char *url) +{ + if(DB_find(url)) { + log_info("Already recorded as installed: %s", url); + } + + FILE *db = DB_open(DB_FILE, "a+"); + check(db, "Failed to open DB file: %s", DB_FILE); + + bstring line = bfromcstr(url); + bconchar(line, '\n'); + int rc = fwrite(line->data, blength(line), 1, db); + check(rc == 1, "Failed to append to the db."); + + return 0; +error: + if(db) DB_close(db); + return -1; +} + + +int DB_find(const char *url) +{ + bstring data = NULL; + bstring line = bfromcstr(url); + int res = -1; + + data = DB_load(); + check(data, "Failed to load: %s", DB_FILE); + + if(binstr(data, 0, line) == BSTR_ERR) { + res = 0; + } else { + res = 1; + } + +error: // fallthrough + if(data) bdestroy(data); + if(line) bdestroy(line); + + return res; +} + + +int DB_init() +{ + apr_pool_t *p = NULL; + apr_pool_initialize(); + apr_pool_create(&p, NULL); + + if(access(DB_DIR, W_OK | X_OK) == -1) { + apr_status_t rc = apr_dir_make_recursive(DB_DIR, + APR_UREAD | APR_UWRITE | APR_UEXECUTE | + APR_GREAD | APR_GWRITE | APR_GEXECUTE, p); + check(rc == APR_SUCCESS, "Failed to make database dir: %s", DB_DIR); + } + + if(access(DB_FILE, W_OK) == -1) { + FILE *db = DB_open(DB_FILE, "w"); + check(db, "Cannot open database: %s", DB_FILE); + DB_close(db); + } + + apr_pool_destroy(p); + return 0; + +error: + apr_pool_destroy(p); + return -1; +} + + +int DB_list() +{ + bstring data = DB_load(); + check(data, "Failed to read load: %s", DB_FILE); + + printf("%s", bdata(data)); + bdestroy(data); + return 0; + +error: + return -1; +} diff --git a/lcthw-remnants-2/devpkg/db.h b/lcthw-remnants-2/devpkg/db.h new file mode 100644 index 0000000..9f54f0f --- /dev/null +++ b/lcthw-remnants-2/devpkg/db.h @@ -0,0 +1,12 @@ +#ifndef _db_h +#define _db_h + +#define DB_FILE "/usr/local/.devpkg/db" +#define DB_DIR "/usr/local/.devpkg" + +int DB_init(); +int DB_list(); +int DB_update(const char *url); +int DB_find(const char *url); + +#endif diff --git a/lcthw-remnants-2/devpkg/dbg.h b/lcthw-remnants-2/devpkg/dbg.h new file mode 100644 index 0000000..a7aaa6a --- /dev/null +++ b/lcthw-remnants-2/devpkg/dbg.h @@ -0,0 +1,30 @@ +#ifndef __dbg_h__ +#define __dbg_h__ + +#include +#include +#include + +#ifdef NDEBUG +#define debug(M, ...) +#else +#define debug(M, ...) fprintf(stderr, "DEBUG %s:%d:%s: " M "\n", __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#endif + +#define clean_errno() (errno == 0 ? "None" : strerror(errno)) + +#define log_err(M, ...) fprintf(stderr, "[ERROR] (%s:%d:%s: errno: %s) " M "\n", __FILE__, __LINE__, __func__, clean_errno(), ##__VA_ARGS__) + +#define log_warn(M, ...) fprintf(stderr, "[WARN] (%s:%d:%s: errno: %s) " M "\n", __FILE__, __LINE__, __func__, clean_errno(), ##__VA_ARGS__) + +#define log_info(M, ...) fprintf(stderr, "[INFO] (%s:%d:%s) " M "\n", __FILE__, __LINE__, __func__, ##__VA_ARGS__) + +#define check(A, M, ...) if(!(A)) { log_err(M, ##__VA_ARGS__); errno=0; goto error; } + +#define sentinel(M, ...) { log_err(M, ##__VA_ARGS__); errno=0; goto error; } + +#define check_mem(A) check((A), "Out of memory.") + +#define check_debug(A, M, ...) if(!(A)) { debug(M, ##__VA_ARGS__); errno=0; goto error; } + +#endif diff --git a/lcthw-remnants-2/devpkg/devpkg.c b/lcthw-remnants-2/devpkg/devpkg.c new file mode 100644 index 0000000..ff76c2b --- /dev/null +++ b/lcthw-remnants-2/devpkg/devpkg.c @@ -0,0 +1,104 @@ +#include +#include +#include +#include +#include + +#include "dbg.h" +#include "db.h" +#include "commands.h" + +int main(int argc, const char const *argv[]) +{ + apr_pool_t *p = NULL; + apr_pool_initialize(); + apr_pool_create(&p, NULL); + + apr_getopt_t *opt; + apr_status_t rv; + + char ch = '\0'; + const char *optarg = NULL; + const char *config_opts = NULL; + const char *install_opts = NULL; + const char *make_opts = NULL; + const char *url = NULL; + enum CommandType request = COMMAND_NONE; + + + rv = apr_getopt_init(&opt, p, argc, argv); + + while(apr_getopt(opt, "I:Lc:m:i:d:SF:B:", &ch, &optarg) == APR_SUCCESS) { + switch(ch) { + case 'I': + request = COMMAND_INSTALL; + url = optarg; + break; + + case 'L': + request = COMMAND_LIST; + break; + + case 'c': + config_opts = optarg; + break; + + case 'm': + make_opts = optarg; + break; + + case 'i': + install_opts = optarg; + break; + + case 'S': + request = COMMAND_INIT; + break; + + case 'F': + request = COMMAND_FETCH; + url = optarg; + break; + + case 'B': + request = COMMAND_BUILD; + url = optarg; + break; + } + } + + switch(request) { + case COMMAND_INSTALL: + check(url, "You must at least give a URL."); + Command_install(p, url, config_opts, make_opts, install_opts); + break; + + case COMMAND_LIST: + DB_list(); + break; + + case COMMAND_FETCH: + check(url != NULL, "You must give a URL."); + Command_fetch(p, url, 1); + log_info("Downloaded to %s and in /tmp/", BUILD_DIR); + break; + + case COMMAND_BUILD: + check(url, "You must at least give a URL."); + Command_build(p, url, config_opts, make_opts, install_opts); + break; + + case COMMAND_INIT: + rv = DB_init(); + check(rv == 0, "Failed to make the database."); + break; + + default: + sentinel("Invalid command given."); + } + + return 0; + +error: + return 1; +} diff --git a/lcthw-remnants-2/devpkg/shell.c b/lcthw-remnants-2/devpkg/shell.c new file mode 100644 index 0000000..5da733b --- /dev/null +++ b/lcthw-remnants-2/devpkg/shell.c @@ -0,0 +1,120 @@ +#include "shell.h" +#include "dbg.h" +#include + +int Shell_exec(Shell template, ...) +{ + apr_pool_t *p = NULL; + int rc = -1; + apr_status_t rv = APR_SUCCESS; + va_list argp; + const char *key = NULL; + const char *arg = NULL; + int i = 0; + + rv = apr_pool_create(&p, NULL); + check(rv == APR_SUCCESS, "Failed to create pool."); + + va_start(argp, template); + + for(key = va_arg(argp, const char *); + key != NULL; + key = va_arg(argp, const char *)) + { + arg = va_arg(argp, const char *); + + for(i = 0; template.args[i] != NULL; i++) { + if(strcmp(template.args[i], key) == 0) { + template.args[i] = arg; + break; // found it + } + } + } + + rc = Shell_run(p, &template); + apr_pool_destroy(p); + va_end(argp); + return rc; + +error: + if(p) { + apr_pool_destroy(p); + } + return rc; +} + +int Shell_run(apr_pool_t *p, Shell *cmd) +{ + apr_procattr_t *attr; + apr_status_t rv; + apr_proc_t newproc; + + rv = apr_procattr_create(&attr, p); + check(rv == APR_SUCCESS, "Failed to create proc attr."); + + rv = apr_procattr_io_set(attr, APR_NO_PIPE, APR_NO_PIPE, APR_NO_PIPE); + check(rv == APR_SUCCESS, "Failed to set IO of command."); + + rv = apr_procattr_dir_set(attr, cmd->dir); + check(rv == APR_SUCCESS, "Failed to set root to %s", cmd->dir); + + rv = apr_procattr_cmdtype_set(attr, APR_PROGRAM_PATH); + check(rv == APR_SUCCESS, "Failed to set cmd type."); + + rv = apr_proc_create(&newproc, cmd->exe, cmd->args, NULL, attr, p); + check(rv == APR_SUCCESS, "Failed to run command."); + + rv = apr_proc_wait(&newproc, &cmd->exit_code, &cmd->exit_why, APR_WAIT); + check(rv == APR_CHILD_DONE, "Failed to wait."); + + check(cmd->exit_code == 0, "%s exited badly.", cmd->exe); + check(cmd->exit_why == APR_PROC_EXIT, "%s was killed or crashed", cmd->exe); + + return 0; + +error: + return -1; +} + +Shell CLEANUP_SH = { + .exe = "rm", + .dir = "/tmp", + .args = {"rm", "-rf", "/tmp/pkg-build", "/tmp/pkg-src.tar.gz", + "/tmp/pkg-src.tar.bz2", "/tmp/DEPENDS", NULL} +}; + +Shell GIT_SH = { + .dir = "/tmp", + .exe = "git", + .args = {"git", "clone", "URL", "pkg-build", NULL} +}; + +Shell TAR_SH = { + .dir = "/tmp/pkg-build", + .exe = "tar", + .args = {"tar", "-xzf", "FILE", "--strip-components", "1", NULL} +}; + +Shell CURL_SH = { + .dir = "/tmp", + .exe = "curl", + .args = {"curl", "-L", "-o", "TARGET", "URL", NULL} +}; + +Shell CONFIGURE_SH = { + .exe = "./configure", + .dir = "/tmp/pkg-build", + .args = {"configure", "OPTS", NULL}, +}; + +Shell MAKE_SH = { + .exe = "make", + .dir = "/tmp/pkg-build", + .args = {"make", "OPTS", NULL} +}; + +Shell INSTALL_SH = { + .exe = "sudo", + .dir = "/tmp/pkg-build", + .args = {"sudo", "make", "TARGET", NULL} +}; diff --git a/lcthw-remnants-2/devpkg/shell.h b/lcthw-remnants-2/devpkg/shell.h new file mode 100644 index 0000000..e966454 --- /dev/null +++ b/lcthw-remnants-2/devpkg/shell.h @@ -0,0 +1,31 @@ +#ifndef _shell_h +#define _shell_h + +#define MAX_COMMAND_ARGS 100 + +#include + +typedef struct Shell { + const char *dir; + const char *exe; + + apr_procattr_t *attr; + apr_proc_t proc; + apr_exit_why_e exit_why; + int exit_code; + + const char *args[MAX_COMMAND_ARGS]; +} Shell; + +int Shell_run(apr_pool_t *p, Shell *cmd); +int Shell_exec(Shell cmd, ...); + +extern Shell CLEANUP_SH; +extern Shell GIT_SH; +extern Shell TAR_SH; +extern Shell CURL_SH; +extern Shell CONFIGURE_SH; +extern Shell MAKE_SH; +extern Shell INSTALL_SH; + +#endif