diff options
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  | 
