diff options
author | Tharre <tharre3@gmail.com> | 2014-04-08 09:19:33 +0200 |
---|---|---|
committer | xRamses <tharre3@gmail.com> | 2014-04-08 09:19:33 +0200 |
commit | c766f073e60c3aa51a3cc1555af87ded5823e5a0 (patch) | |
tree | 43595070e833b251b3f7ac7e94e5e992918194ef /src | |
download | redo-c766f073e60c3aa51a3cc1555af87ded5823e5a0.tar.gz redo-c766f073e60c3aa51a3cc1555af87ded5823e5a0.tar.xz redo-c766f073e60c3aa51a3cc1555af87ded5823e5a0.zip |
Initial codebase
Diffstat (limited to 'src')
-rw-r--r-- | src/build.c | 262 | ||||
-rw-r--r-- | src/build.h | 16 | ||||
-rw-r--r-- | src/dbg.h | 28 | ||||
-rw-r--r-- | src/redo-ifchange.c | 10 | ||||
-rw-r--r-- | src/redo.c | 15 | ||||
-rw-r--r-- | src/util.c | 60 | ||||
-rw-r--r-- | src/util.h | 13 |
7 files changed, 404 insertions, 0 deletions
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 <stdio.h> +#include <unistd.h> +#include <sys/wait.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <string.h> +#include <stdbool.h> +#include <assert.h> + +#include <libgen.h> /* 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 <stdbool.h> +#include <stddef.h> +#include <sys/types.h> + +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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +// #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 <stdio.h> + +#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 <stdio.h> + +#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 <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <stdarg.h> +#include <assert.h> + +#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 <stdlib.h> +#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 |