aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorTharre <tharre3@gmail.com>2014-04-08 09:19:33 +0200
committerxRamses <tharre3@gmail.com>2014-04-08 09:19:33 +0200
commitc766f073e60c3aa51a3cc1555af87ded5823e5a0 (patch)
tree43595070e833b251b3f7ac7e94e5e992918194ef /src
downloadredo-c766f073e60c3aa51a3cc1555af87ded5823e5a0.tar.gz
redo-c766f073e60c3aa51a3cc1555af87ded5823e5a0.tar.xz
redo-c766f073e60c3aa51a3cc1555af87ded5823e5a0.zip
Initial codebase
Diffstat (limited to 'src')
-rw-r--r--src/build.c262
-rw-r--r--src/build.h16
-rw-r--r--src/dbg.h28
-rw-r--r--src/redo-ifchange.c10
-rw-r--r--src/redo.c15
-rw-r--r--src/util.c60
-rw-r--r--src/util.h13
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