From c766f073e60c3aa51a3cc1555af87ded5823e5a0 Mon Sep 17 00:00:00 2001 From: Tharre Date: Tue, 8 Apr 2014 09:19:33 +0200 Subject: Initial codebase --- .travis.yml | 8 ++ LICENSE | 21 +++++ README.md | 10 ++ Redofile | 25 +++++ autogen.sh | 4 + build.sh | 11 +++ configure.ac | 30 ++++++ out/CC.do | 12 +++ out/config.sh | 8 ++ out/default.o.do | 2 + out/redo-ifchange.do | 5 + out/redo.do | 5 + src/build.c | 262 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/build.h | 16 ++++ src/dbg.h | 28 ++++++ src/redo-ifchange.c | 10 ++ src/redo.c | 15 +++ src/util.c | 60 ++++++++++++ src/util.h | 13 +++ 19 files changed, 545 insertions(+) create mode 100644 .travis.yml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 Redofile create mode 100755 autogen.sh create mode 100755 build.sh create mode 100644 configure.ac create mode 100644 out/CC.do create mode 100644 out/config.sh create mode 100644 out/default.o.do create mode 100644 out/redo-ifchange.do create mode 100644 out/redo.do create mode 100644 src/build.c create mode 100644 src/build.h create mode 100644 src/dbg.h create mode 100644 src/redo-ifchange.c create mode 100644 src/redo.c create mode 100644 src/util.c create mode 100644 src/util.h diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c57c818 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,8 @@ +language: c +compiler: + - gcc + - clang +script: ./autogen.sh && ./configure && ./build.sh +before_install: + - sudo apt-get update -qq + - sudo apt-get install -q -y libssl-dev diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cf89105 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Tharre + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b722ebb --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# What is redo? +`redo` provides an elegant and minimalistic way of rebuilding files from source. The design stems from Daniel J. Bernstein, who published a basic concept about how `redo` should work on his [website](http://cr.yp.to/redo.html). + +If you want more detailed information about `redo`, a working python implementation of `redo` already exists [here](https://github.com/apenwarr/redo). + +# About this implementation +The goal of this project is not only to implement `redo` in C, but also to rethink and improve it. This also means that this project will not try be compatible with other implementations of `redo`, although this shouldn't prove problematic. + +# Project status +The is work in progress, many features are still missing and the behaviour is not set in stone yet. Missing features include dependency checking, parallel builds, recursive checking and probably a lot more. \ No newline at end of file diff --git a/Redofile b/Redofile new file mode 100644 index 0000000..c714912 --- /dev/null +++ b/Redofile @@ -0,0 +1,25 @@ +# define all project wide variables + +export ROOTDIR=$(pwd) +export SRCDIR=$ROOTDIR/src +export OUTDIR=$ROOTDIR/out +export VERSION="pre-0.01" + +# TODO: improve this + +if [ "$1" = "all" ]; then + redo-ifchange $OUTDIR/redo $OUTDIR/redo-ifchange +elif [ "$1" = "clean" ]; then + rm -rf $OUTDIR/*.tmp $OUTDIR/*.o $OUTDIR/redo $OUTDIR/redo-ifchange $OUTDIR/CC + # autoconf stuff + rm -rf autom4te.cache config.h.in configure config.status config.log config.h +elif [ "$1" = "install" ]; then + redo-ifchange all + sudo install $OUTDIR/redo /usr/bin + sudo install $OUTDIR/redo-ifchange /usr/bin +elif [ "$1" = "test" ]; then + echo "Sadly there are no tests yet ..." +elif [ "$1" = "zip" ]; then + redo-ifchange clean # we always want to zip the clean workstate + zip -r redo.zip * +fi diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..a8a0435 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,4 @@ +#!/bin/sh +# Just run autoreconf for the moment + +autoreconf diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..9bfbbd1 --- /dev/null +++ b/build.sh @@ -0,0 +1,11 @@ +#!/bin/sh -e + +. out/config.sh +$CC $CFLAGS -o out/util.o -c src/util.c +$CC $CFLAGS -o out/build.o -c src/build.c +$CC $CFLAGS -o out/redo.o -c src/redo.c +$CC $CFLAGS -o out/redo-ifchange.o -c src/redo-ifchange.c +$CC -o out/redo out/redo.o out/util.o out/build.o $LDFLAGS +$CC -o out/redo-ifchange out/redo.o out/util.o out/build.o $LDFLAGS + +echo "Finished compiling" diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..9b73edc --- /dev/null +++ b/configure.ac @@ -0,0 +1,30 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ([2.68]) +AC_INIT([redo], [pre-0.01], [BUG-REPORT-ADDRESS]) +AC_CONFIG_SRCDIR([src/redo-ifchange.c]) +AC_CONFIG_HEADERS([config.h]) + +# Checks for programs. +AC_PROG_CXX +AC_PROG_CC + +# Checks for libraries. + +# Checks for header files. +AC_CHECK_HEADERS([stddef.h stdint.h stdlib.h string.h unistd.h]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_CHECK_HEADER_STDBOOL +AC_TYPE_OFF_T +AC_TYPE_PID_T +AC_TYPE_SIZE_T + +# Checks for library functions. +AC_FUNC_FORK +AC_FUNC_MALLOC +AC_FUNC_REALLOC +AC_CHECK_FUNCS([strdup strerror strrchr]) + +AC_OUTPUT diff --git a/out/CC.do b/out/CC.do new file mode 100644 index 0000000..f18177a --- /dev/null +++ b/out/CC.do @@ -0,0 +1,12 @@ +redo-ifchange config.sh +. ./config.sh + +exec >$3 +cat <<-EOF + redo-ifchange \$SRCDIR/\$2.c + $CC $CFLAGS -MD -MF \$3.deps -o \$3 -c \$SRCDIR/\$2.c + read DEPS <\$3.deps + rm -f \$3.deps + redo-ifchange \${DEPS#*:} +EOF +chmod +x $3 diff --git a/out/config.sh b/out/config.sh new file mode 100644 index 0000000..7a1f34c --- /dev/null +++ b/out/config.sh @@ -0,0 +1,8 @@ +if type "clang" > /dev/null; then + PREF="clang" +else + PREF="gcc" +fi +CC=${CC-$PREF} +CFLAGS="-g -Wall -Wextra -std=c99 -pedantic" +LDFLAGS=-lcrypto diff --git a/out/default.o.do b/out/default.o.do new file mode 100644 index 0000000..d48bdd1 --- /dev/null +++ b/out/default.o.do @@ -0,0 +1,2 @@ +redo-ifchange CC +. ./CC "$@" diff --git a/out/redo-ifchange.do b/out/redo-ifchange.do new file mode 100644 index 0000000..84941ce --- /dev/null +++ b/out/redo-ifchange.do @@ -0,0 +1,5 @@ +. ./config.sh + +DEPS="redo-ifchange.o build.o util.o" +redo-ifchange $DEPS config.sh +$CC -o $3 $DEPS $LDFLAGS diff --git a/out/redo.do b/out/redo.do new file mode 100644 index 0000000..dce65d4 --- /dev/null +++ b/out/redo.do @@ -0,0 +1,5 @@ +. ./config.sh + +DEPS="redo.o build.o util.o" +redo-ifchange $DEPS config.sh +$CC -o $3 $DEPS $LDFLAGS diff --git a/src/build.c b/src/build.c new file mode 100644 index 0000000..764ec2b --- /dev/null +++ b/src/build.c @@ -0,0 +1,262 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include /* dirname(), basename() */ + +#include "build.h" +#include "util.h" +#define __FILENAME__ "build.c" +#include "dbg.h" + +/* TODO: more useful return codes? */ + +const char do_file_ext[] = ".do"; +const char default_name[] = "default"; +const char temp_ext[] = ".redoing.tmp"; + +int build_target(const char *target) { + assert(target); + + int retval = 0; + printf("redo %s\n", target); + + /* get the do-file which we are going to execute */ + char *do_file = get_do_file(target); + if (do_file == NULL) { + if (file_exists(target)) { + /* if our target file has no do file associated but exists, + then we treat it as a source */ + /* TODO: write dependencies */ + goto exit; + } + fprintf(stderr, "%s couldn't be built because no " + "suitable do-file exists\n", target); + retval = 1; + goto exit; + } + + debug("Using do-file %s\n", do_file); + + char *temp_output = concat(2, target, temp_ext); + + pid_t pid = fork(); + if (pid == -1) { + /* failure */ + fatal("redo: failed to fork() new process"); + } else if (pid == 0) { + /* child */ + + /* change directory to our target */ + char *dirc = ec_strdup(target); + char *dtarget = dirname(dirc); + if (chdir(dtarget) == -1) + fatal("redo: failed to change directory to %s", dtarget); + + free(dirc); + + /* target is now in the cwd so change path accordingly */ + char *btarget = xbasename(target); + char *bdo_file = xbasename(do_file); + + /* read and parse shebang */ + FILE *fp = fopen(bdo_file, "rb+"); + if (!fp) + fatal("redo: failed to open %s", bdo_file); + + const size_t bufsize = 1024; + char buf[bufsize]; + + buf[ fread(buf, 1, sizeof(buf)-1, fp) ] = '\0'; + if (ferror(fp)) + fatal("redo: failed to read from %s", bdo_file); + + fclose(fp); + + char **argv; + size_t i = 0; + if (buf[0] == '#' && buf[1] == '!') { + argv = parsecmd(&buf[2], &i, 5); + } else { + argv = ec_malloc(7 * sizeof(char*)); + argv[i++] = "/bin/sh"; + argv[i++] = "-e"; + } + + argv[i++] = bdo_file; + argv[i++] = (char*) btarget; + char *basename = remove_ext(btarget); + argv[i++] = basename; + argv[i++] = temp_output; + argv[i] = NULL; + + execv(argv[0], argv); + + /* execv should never return */ + fatal("redo: failed to replace the child process with %s", argv[0]); + } + + /* parent */ + int returncode; + waitpid(pid, &returncode, 0); + bool remove_temp = true; + + if (WIFEXITED(returncode)) { + if (0 != returncode) { + fprintf(stderr, "redo: invoked do-file %s failed with %d\n", + do_file, WEXITSTATUS(returncode)); + } else { + /* successfull */ + + /* if the file is 0 bytes long we delete it */ + off_t f_size = fsize(temp_output); + if (f_size == -1) { + if (errno != ENOENT) + fatal("redo: failed to determine the size of %s", temp_output); + } else if (f_size) + remove_temp = false; + } + } else { + /* something very wrong happened with the child */ + fprintf(stderr, "redo: invoked do-file did not terminate correctly\n"); + } + + if (remove_temp) { + if (remove(temp_output)) + if (errno != ENOENT) + fatal("redo: failed to remove %s", temp_output); + } else { + if (rename(temp_output, target)) + fatal("redo: failed to rename %s to %s", temp_output, target); + } + + free(temp_output); + exit: + free(do_file); + + return retval; +} + +/* Returns the right do-file for target */ +char *get_do_file(const char *target) { + assert(target); + /* target + ".do" */ + char *temp = concat(2, target, do_file_ext); + if (file_exists(temp)) + return temp; + free(temp); + + /* default + get_extension(target) + ".do" */ + temp = concat(3, default_name, take_extension(target), do_file_ext); + if (file_exists(temp)) + return temp; + free(temp); + + /* Redofile */ + temp = strdup("Redofile"); + if (file_exists(temp)) + return temp; + free(temp); + + return NULL; +} + +/* Returns the extension of the target or the empty string if none was found */ +char *take_extension(const char *target) { + assert(target); + char *temp = strrchr(target, '.'); + if (temp) + return temp; + else { + return ""; + } +} + +/* Checks if target exists and prints a debug message if access() failed + except if it failed with ENOENT. */ +bool file_exists(const char *target) { + assert(target); + if (!access(target, F_OK)) + return true; + if (errno != ENOENT) + debug("Failed to access %s: %s\n", target, strerror(errno)); + return false; +} + +/* Returns a new copy of str with the extension removed, where the extension is + everything behind the last dot, including the dot. */ +char *remove_ext(const char *str) { + assert(str); + size_t len; + char *ret, *dot = NULL; + + for (len = 0; str[len]; ++len) + if (str[len] == '.') + dot = (char*) &str[len]; + + if (dot) /* recalculate length to only reach just before the last dot */ + len = dot - str; + + ret = ec_malloc(len+1); + memcpy(ret, str, len); + ret[len] = '\0'; + + return ret; +} + +/* Breaks cmd at spaces and stores a pointer to each argument in the returned + array. The index i is incremented to point to the next free pointer. The + returned array is guaranteed to have at least keep_free entries left */ +char **parsecmd(char *cmd, size_t *i, size_t keep_free) { + assert(cmd); + size_t argv_len = 16; + char **argv = ec_malloc(argv_len * sizeof(char*)); + size_t j = 0; + bool prev_space = true; + for (;; ++j) { + switch (cmd[j]) { + case ' ': + cmd[j] = '\0'; + prev_space = true; + break; + case '\n': + case '\r': + cmd[j] = '\0'; + case '\0': + return argv; + default: + if (!prev_space) + break; + /* check if we have enough space */ + while (*i+keep_free >= argv_len) { + argv_len *= 2; + debug("Reallocating memory (now %zu)\n", argv_len); + /* TODO: replace realloc? */ + char **new_ptr = realloc(argv, argv_len * sizeof(char*)); + if (!new_ptr) + fatal("redo: couldn't realloc %zu bytes", + argv_len * sizeof(char*)); + argv = new_ptr; + } + + prev_space = false; + argv[*i] = &cmd[j]; + ++*i; + } + } +} + +/* Returns the size of fn */ +off_t fsize(const char *fn) { + struct stat st; + + if (stat(fn, &st)) + return -1; + + return st.st_size; +} diff --git a/src/build.h b/src/build.h new file mode 100644 index 0000000..418f4b0 --- /dev/null +++ b/src/build.h @@ -0,0 +1,16 @@ +#ifndef __RBUILD_H__ +#define __RBUILD_H__ + +#include +#include +#include + +extern char *get_do_file(const char *target); +extern int build_target(const char *target); +extern bool file_exists(const char *target); +extern char *take_extension(const char *target); +extern char *remove_ext(const char *str); +extern char **parsecmd(char *cmd, size_t *i, size_t keep_free); +extern off_t fsize(const char *fn); + +#endif diff --git a/src/dbg.h b/src/dbg.h new file mode 100644 index 0000000..014c358 --- /dev/null +++ b/src/dbg.h @@ -0,0 +1,28 @@ +#ifndef __DBG_H__ +#define __DBG_H__ + +#include +#include +#include +#include + +// #define __BFILE__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) +#ifndef __FILENAME__ +#define __FILENAME__ __FILE__ +#endif + +/* helper functions which help in replacing the GNU extension ##__VA_ARGS__ */ +#define STRINGIFY(x) #x +#define LOG_HELPER(f,l,...) fprintf(stderr, "("f":"STRINGIFY(l)"): "__VA_ARGS__) +#define FATAL_HELPER(M, ...) log_err(M ": %s\n", __VA_ARGS__) + +#ifdef NDEBUG +#define debug(...) +#else +#define debug(...) log_err(__VA_ARGS__) +#endif + +#define log_err(...) LOG_HELPER(__FILENAME__, __LINE__, __VA_ARGS__) +#define fatal(...) {FATAL_HELPER(__VA_ARGS__, strerror(errno)); exit(EXIT_FAILURE);} + +#endif diff --git a/src/redo-ifchange.c b/src/redo-ifchange.c new file mode 100644 index 0000000..9415776 --- /dev/null +++ b/src/redo-ifchange.c @@ -0,0 +1,10 @@ +#include + +#include "build.h" + +int main(int argc, char *argv[]) { + int i; + for (i = 1; i < argc; ++i) { + build_target(argv[i]); + } +} diff --git a/src/redo.c b/src/redo.c new file mode 100644 index 0000000..c745c54 --- /dev/null +++ b/src/redo.c @@ -0,0 +1,15 @@ +#include + +#include "build.h" +#include "util.h" + +int main(int argc, char *argv[]) { + if (argc < 2) { + build_target("all"); + } else { + int i; + for (i = 1; i < argc; ++i) { + build_target(argv[i]); + } + } +} diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..e67ec18 --- /dev/null +++ b/src/util.c @@ -0,0 +1,60 @@ +#include +#include +#include +#include +#include + +#include "util.h" +#define __FILENAME__ "util.c" +#include "dbg.h" + +#include "../config.h" + +/* A safe malloc wrapper. */ +void *ec_malloc(size_t size) { + void *ptr = malloc(size); + if(ptr == NULL) + fatal("redo: cannot allocate %zu bytes", size); + + return ptr; +} + +/* For concating multiple strings into a single larger one. */ +char *concat(size_t count, ...) { + assert(count > 0); + va_list ap, ap2; + va_start(ap, count); + va_copy(ap2, ap); + size_t i, size = 0, args_len[count]; + for (i = 0; i < count; ++i) { + args_len[i] = strlen(va_arg(ap, char*)); + size += args_len[i]; + } + size++; + char *result = ec_malloc(size); + /* debug("Allocated %zu bytes at %p\n", size, result); */ + uintptr_t offset = 0; + for (i = 0; i < count; ++i) { + strcpy(&result[offset], va_arg(ap2, char*)); + offset += args_len[i]; + } + va_end(ap); + va_end(ap2); + return result; +} + +char *xbasename(const char *path) { + assert(path); + char *ptr = strrchr(path, '/'); + return ptr? ptr+1 : (char*) path; +} + +char *ec_strdup(const char* str) { + assert(str); + size_t len = strlen(str) + 1; + char *ptr = malloc(len); + if (!ptr) + fatal("redo: failed to duplicate string"); + + return memcpy(ptr, str, len);; +} diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..40780d8 --- /dev/null +++ b/src/util.h @@ -0,0 +1,13 @@ +#ifndef __RUTIL_H__ +#define __RUTIL_H__ + +#include +#include "../config.h" + +extern char *concat(size_t count, ...); +extern void *ec_malloc(size_t size); +extern char *ec_strdup(const char* str); +extern char *strdup(const char *str); +extern char *xbasename(const char *path); + +#endif -- cgit v1.2.3-70-g09d2