~ac/chnode

22228bcb57acd54a91808fec30eed6955111f7ff — Ant Cosentino 3 years ago b81b939
lots of cleanup and some fixes
2 files changed, 218 insertions(+), 103 deletions(-)

M chnode.c
M chnode.h
M chnode.c => chnode.c +1 -1
@@ 2,5 2,5 @@
#include "chnode.h"

int main(int argc, char** argv) {
	return dispatch_command(argc, argv);
	return chnode(argc, argv);
}

M chnode.h => chnode.h +217 -102
@@ 17,6 17,7 @@
#warning "chnode is being installed with the default global prefix: /usr/local"
#endif

#define NVMRC_MAX_LENGTH 50
#define NODEJS_DIST_BASE_URI "https://nodejs.org/dist"

#include <errno.h>


@@ 31,9 32,9 @@

static struct chnode {
	bool is_error;
	bool is_recoverable;
	bool installing;
	bool restoring;
	char* allocation;

	struct args {
		bool help;


@@ 77,22 78,35 @@ static void trap_exit() {
	printf("TRACE: enter on_exit\n");
	#endif

	if (!ctx.is_error) return;
	if (!ctx.is_recoverable) {
		printf("WARNING: Detected an unrecoverable error\n");
		printf("WARNING: Consider nuking your chnode directory before retrying\n");
	if (ctx.allocation) {
		#ifdef TRACE
		printf("TRACE: enter ctx.allocation branch\n");
		#endif
		free(ctx.allocation);
	}

	if (!ctx.is_error) {
		#ifdef TRACE
		printf("TRACE: enter not ctx.is_error branch\n");
		#endif
		return;
	}

	if (!ctx.paths.version) return;

	char* clean_cmd;
	bool format_error = asprintf(&clean_cmd, "rm -rf %s", ctx.paths.version) < 0;
	if (format_error) {
		printf("WARNING: Failed to clean up .chnode directory\n");
		printf("WARNING: Failed to format cleanup command\n");
		return;
	}

	#ifdef TRACE
	printf("TRACE: clean_cmd %s\n", clean_cmd);
	#endif

	if (system(clean_cmd)) {
		printf("WARNING: Failed to clean up %s\n", ctx.paths.version);
		printf("WARNING: Failed to clean up directory %s\n", ctx.paths.version);
		return;
	}
}


@@ 103,7 117,7 @@ static size_t curl_on_data(void* ptr, size_t bytes, size_t nmemb, void* stream) 

static bool http_get_to_file(char* uri, FILE* f) {
	#ifdef TRACE
	printf("TRACE: enter http_get_to_file\n");
	printf("TRACE: enter http_get_to_file: %s\n", uri);
	#endif
	CURL* curl;
	curl_global_init(CURL_GLOBAL_ALL);


@@ 114,12 128,22 @@ static bool http_get_to_file(char* uri, FILE* f) {
	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_on_data);
	curl_easy_setopt(curl, CURLOPT_WRITEDATA, f);
	CURLcode res = curl_easy_perform(curl);
	curl_easy_cleanup(curl);
	curl_global_cleanup();
	return (
	bool success = (
		res != CURLE_HTTP_RETURNED_ERROR &&
		res == CURLE_OK
	);
	#ifdef TRACE
	if (!success) {
		printf("TRACE: got curl error %d: %s\n", res, curl_easy_strerror(res));
	}
	#endif

	curl_easy_cleanup(curl);
	curl_global_cleanup();
	#ifdef TRACE
	printf("TRACE: enter http_get_to_file\n");
	#endif
	return success;
}

static void show_intro(void) {


@@ 136,10 160,13 @@ static void show_usage(void) {
	#ifdef TRACE
	printf("TRACE: enter show_usage\n");
	#endif
	printf("Usage: chnode <version>\n");
	printf("Usage:\n");
	printf("\tchnode use\n");
	printf("\tchnode <version>\n");
	printf("\tchnode < /some/version/file\n");
}

static bool show_current() {
static bool show_current(void) {
	#ifdef TRACE
	printf("TRACE: enter show_current\n");
	#endif


@@ 179,29 206,120 @@ static bool parse_version(char* version) {
	#ifdef TRACE
	printf("TRACE: enter parse_version\n");
	#endif
	size_t v_ptr = 0;
	char* versions[3];
	char* next_token = strtok(version, ".");
	if (!next_token) return false;
	versions[v_ptr] = next_token;
	while ((next_token = strtok(NULL, "."))) {
		v_ptr += 1;
		if (v_ptr > 2) return false;
		versions[v_ptr] = next_token;
	}
	if (v_ptr != 2) return false;

	ctx.version.major = versions[0];
	ctx.version.minor = versions[1];
	ctx.version.patch = versions[2];
	char* semver_parts[3];
	int i = 0;
	for (
		char* next_token = strtok(version, ".");
		next_token;
		next_token = strtok(NULL, ".")
	) {
		#ifdef TRACE
		printf("TRACE: idx %d: token %s\n", i, next_token);
		#endif
		semver_parts[i] = next_token;
		i += 1;
	}

	// FIXME
	// the version can be a.b.c
	if (i != 3) {
		printf("Failed to parse version, exiting.\n");
		return false;

		// FIXME
		// support codenames
	}

	ctx.args.version = true;
	ctx.version.major = semver_parts[0];
	ctx.version.minor = semver_parts[1];
	ctx.version.patch = semver_parts[2];

	return true;
}

static bool chnode_dir() {
static bool read_fd(int fd) {
	#ifdef TRACE
	printf("TRACE: enter read_fd\n");
	#endif

	struct stat fd_stat;
	if (fstat(fd, &fd_stat) == -1) {
		perror("Failed to get length of file descriptor");
		return false;
	}

	if (!fd_stat.st_size) {
		#ifdef TRACE
		printf("TRACE: enter help branch\n");
		#endif
		ctx.args.help = true;
		return true;
	}

	if (fd_stat.st_size > NVMRC_MAX_LENGTH) {
		printf("Input byte length exceeds limit.\n");
		return false;
	}

	FILE* input = fdopen(fd, "rb");
	if (!input) {
		perror("Failed to open file descriptor for reading");
		return false;
	}

	ctx.allocation = malloc(fd_stat.st_size + 1);
	if (!ctx.allocation) {
		perror("Failed to allocate memory for input bytes");
		return false;
	}

	fread(ctx.allocation, 1, fd_stat.st_size, input);
	ctx.allocation[fd_stat.st_size] = 0;

	if (fclose(input) == EOF) {
		perror("Failed to close file descriptor");
		return false;
	}

	return parse_version(ctx.allocation);
}

static bool parse_nvmrc(void) {
	#ifdef TRACE
	printf("TRACE: enter parse_nvmrc\n");
	#endif

	char* cwd = getenv("PWD");
	if (!cwd) {
		printf("Failed to determine current working directory.\n");
		return false;
	}

	char* nvmrc_path;
	bool format_error = asprintf(&nvmrc_path, "%s/.nvmrc", cwd) < 0;
	if (format_error) {
		printf("Failed to construct path to .nvmrc\n");
		return false;
	}

	FILE* nvmrc_file = fopen(nvmrc_path, "rb");
	if (!nvmrc_file) {
		perror(nvmrc_path);
		return false;
	}

	int nvmrc_fd = fileno(nvmrc_file);
	return read_fd(nvmrc_fd);
}

static bool parse_stdin(void) {
	#ifdef TRACE
	printf("TRACE: enter parse_stdin\n");
	#endif

	return read_fd(STDIN_FILENO);
}

static bool chnode_dir(void) {
	#ifdef TRACE
	printf("TRACE: enter chnode_dir\n");
	#endif


@@ 214,7 332,6 @@ static bool chnode_dir() {
	if (format_error) {
		perror("Failed to construct path to chnode directory");
		ctx.is_error = true;
		ctx.is_recoverable = false;
		return false;
	}



@@ 222,14 339,13 @@ static bool chnode_dir() {
	if (mkdir_error && errno != EEXIST) {
		perror("Failed to make chnode directory");
		ctx.is_error = true;
		ctx.is_recoverable = false;
		return false;
	}

	return true;
}

static bool version_dir() {
static bool version_dir(void) {
	#ifdef TRACE
	printf("TRACE: enter version_dir\n");
	#endif


@@ 245,7 361,6 @@ static bool version_dir() {
	if (format_error) {
		perror("Failed to construct path to directory for given version");
		ctx.is_error = true;
		ctx.is_recoverable = false;
		return false;
	}



@@ 259,7 374,6 @@ static bool version_dir() {
		if (mkdir_error && errno != EEXIST) {
			perror("Failed to ensure version directory exists");
			ctx.is_error = true;
			ctx.is_recoverable = false;
			return false;
		}
		ctx.installing = true;


@@ 269,14 383,13 @@ static bool version_dir() {
	if (nodejs_path_error) {
		perror("Failed to ensure version directory exists");
		ctx.is_error = true;
		ctx.is_recoverable = false;
		return false;
	}
	ctx.restoring = true;
	return true;
}

static bool release_dir() {
static bool release_dir(void) {
	#ifdef TRACE
	printf("TRACE: enter release_dir\n");
	#endif


@@ 288,7 401,6 @@ static bool release_dir() {
	if (format_error) {
		perror("Failed to construct path to directory for given version");
		ctx.is_error = true;
		ctx.is_recoverable = false;
		return false;
	}



@@ 300,7 412,6 @@ static bool release_dir() {
		if (mkdir_error && errno != EEXIST) {
			perror("Failed to ensure release directory exists");
			ctx.is_error = true;
			ctx.is_recoverable = false;
			return false;
		}
		return true;


@@ 309,13 420,12 @@ static bool release_dir() {
	if (nodejs_release_path_error) {
		perror("Failed to ensure release directory exists");
		ctx.is_error = true;
		ctx.is_recoverable = false;
		return false;
	}
	return true;
}

static bool download_and_verify() {
static bool download_and_verify(void) {
	#ifdef TRACE
	printf("TRACE: enter download_and_verify\n");
	#endif


@@ 337,7 447,6 @@ static bool download_and_verify() {
	if (format_error) {
		perror("Failed to construct tarball file path");
		ctx.is_error = true;
		ctx.is_recoverable = true;
		return false;
	}



@@ 354,7 463,6 @@ static bool download_and_verify() {
	if (format_error) {
		perror("Failed to construct URI for given version");
		ctx.is_error = true;
		ctx.is_recoverable = true;
		return false;
	}



@@ 369,7 477,6 @@ static bool download_and_verify() {
	if (format_error) {
		perror("Failed to construct path for file download");
		ctx.is_error = true;
		ctx.is_recoverable = true;
		return false;
	}



@@ 377,7 484,6 @@ static bool download_and_verify() {
	if (!nodejs_tarball) {
		perror("Failed to open file path for download");
		ctx.is_error = true;
		ctx.is_recoverable = true;
		return false;
	}



@@ 385,30 491,40 @@ static bool download_and_verify() {
	if (fclose(nodejs_tarball)) {
		perror("Failed to close reference to tarball");
		ctx.is_error = true;
		ctx.is_recoverable = true;
		return false;
	}
	if (!downloaded) {
		printf("Failed to download given version\n");
		ctx.is_error = true;
		ctx.is_recoverable = true;
		return false;
	}

	#ifdef TRACE
	printf(
		"TRACE: major %s minor %s patch %s\n",
		ctx.version.major,
		ctx.version.minor,
		ctx.version.patch
	);
	#endif

	format_error = asprintf(
		&ctx.paths.shasums_uri,
		"%s/v%s.%s.%s/SHASUMS256.txt",
		"%s/v%s.%s.%s/%s",
		NODEJS_DIST_BASE_URI,
		ctx.version.major,
		ctx.version.minor,
		ctx.version.patch
		ctx.version.patch,
		"SHASUMS256.txt"
	) < 0;
	if (format_error) {
		perror("Failed to construct URI to SHASUMS file");
		ctx.is_error = true;
		ctx.is_recoverable = true;
		return false;
	}
	#ifdef TRACE
	printf("TRACE: shasums_uri %s\n", ctx.paths.shasums_uri);
	#endif

	format_error = asprintf(
		&ctx.paths.shasums,


@@ 418,7 534,6 @@ static bool download_and_verify() {
	if (format_error) {
		perror("Failed to construct path for file download");
		ctx.is_error = true;
		ctx.is_recoverable = true;
		return false;
	}



@@ 426,7 541,6 @@ static bool download_and_verify() {
	if (!nodejs_shasums) {
		perror("Failed to open file path for download");
		ctx.is_error = true;
		ctx.is_recoverable = true;
		return false;
	}



@@ 434,13 548,11 @@ static bool download_and_verify() {
	if (fclose(nodejs_shasums)) {
		perror("Failed to close reference to SHASUMS file");
		ctx.is_error = true;
		ctx.is_recoverable = true;
		return false;
	}
	if (!downloaded) {
		printf("Failed to download given SHASUMS file\n");
		ctx.is_error = true;
		ctx.is_recoverable = true;
		return false;
	}



@@ 457,14 569,13 @@ static bool download_and_verify() {
	if (cmd_status != 0) {
		printf("Failed to verify release signatures for given version. Exiting...\n");
		ctx.is_error = true;
		ctx.is_recoverable = true;
		return false;
	}

	return true;
}

static bool extract_tarball() {
static bool extract_tarball(void) {
	#ifdef TRACE
	printf("TRACE: enter extract_tarball\n");
	#endif


@@ 484,7 595,6 @@ static bool extract_tarball() {
	if (format_error) {
		perror("Failed to construct command for extracting tarball");
		ctx.is_error = true;
		ctx.is_recoverable = true;
		return false;
	}



@@ 492,7 602,6 @@ static bool extract_tarball() {
	if (cmd_status == 127 || cmd_status == -1 || cmd_status != 0) {
		printf("Failed to extract tarball due to error %d. Exiting...\n", cmd_status);
		ctx.is_error = true;
		ctx.is_recoverable = true;
		return false;
	}



@@ 500,7 609,7 @@ static bool extract_tarball() {
	return true;
}

static bool unlink_symlinks() {
static bool unlink_symlinks(void) {
	#ifdef TRACE
	printf("TRACE: enter unlink_symlinks\n");
	#endif


@@ 516,13 625,11 @@ static bool unlink_symlinks() {
		format_error = asprintf(&cmds[i], "%s/bin/%s", PREFIX, binaries[i]) < 0;
		if (format_error) {
			ctx.is_error = true;
			ctx.is_recoverable = true;
			return false;
		}
		if (unlink(cmds[i]) == -1 && errno != ENOENT) {
			perror(cmds[i]);
			ctx.is_error = true;
			ctx.is_recoverable = true;
			return false;
		}
	}


@@ 530,52 637,49 @@ static bool unlink_symlinks() {
	return true;
}

static bool mk_symlinks() {
static bool mk_symlinks(void) {
	#ifdef TRACE
	printf("TRACE: enter mk_symlinks\n");
	#endif

	// FIXME
	// parameterise this process so the compiler can unroll it (hopefully)

	bool format_error, symlink_error;

	format_error = asprintf(&ctx.paths.node_src, "%s/bin/node", ctx.paths.release) < 0;
	if (format_error) {
		ctx.is_error = true;
		ctx.is_recoverable = true;
		return false;
	}

	format_error = asprintf(&ctx.paths.node_dst, "%s/bin/node", PREFIX) < 0;
	if (format_error) {
		ctx.is_error = true;
		ctx.is_recoverable = true;
		return false;
	}

	format_error = asprintf(&ctx.paths.npm_src, "%s/bin/npm", ctx.paths.release) < 0;
	if (format_error) {
		ctx.is_error = true;
		ctx.is_recoverable = true;
		return false;
	}

	format_error = asprintf(&ctx.paths.npm_dst, "%s/bin/npm", PREFIX) < 0;
	if (format_error) {
		ctx.is_error = true;
		ctx.is_recoverable = true;
		return false;
	}

	format_error = asprintf(&ctx.paths.npx_src, "%s/bin/npx", ctx.paths.release) < 0;
	if (format_error) {
		ctx.is_error = true;
		ctx.is_recoverable = true;
		return false;
	}

	format_error = asprintf(&ctx.paths.npx_dst, "%s/bin/npx", PREFIX) < 0;
	if (format_error) {
		ctx.is_error = true;
		ctx.is_recoverable = true;
		return false;
	}



@@ 583,7 687,6 @@ static bool mk_symlinks() {
	if (symlink_error) {
		perror(ctx.paths.node_dst);
		ctx.is_error = true;
		ctx.is_recoverable = true;
		return false;
	}



@@ 591,7 694,6 @@ static bool mk_symlinks() {
	if (symlink_error) {
		perror(ctx.paths.npm_dst);
		ctx.is_error = true;
		ctx.is_recoverable = true;
		return false;
	}



@@ 599,14 701,13 @@ static bool mk_symlinks() {
	if (symlink_error) {
		perror(ctx.paths.npx_dst);
		ctx.is_error = true;
		ctx.is_recoverable = true;
		return false;
	}

	return true;
}

static bool test_binaries() {
static bool test_binaries(void) {
	#ifdef TRACE
	printf("TRACE: enter test_binaries\n");
	#endif


@@ 617,14 718,12 @@ static bool test_binaries() {
	format_error = asprintf(&test_node, "%s -v", ctx.paths.node_dst) < 0;
	if (format_error) {
		ctx.is_error = true;
		ctx.is_recoverable = true;
		return false;
	}

	format_error = asprintf(&test_npm, "%s -v", ctx.paths.npm_dst) < 0;
	if (format_error) {
		ctx.is_error = true;
		ctx.is_recoverable = true;
		return false;
	}



@@ 639,55 738,66 @@ static bool parse_arguments(int argc, char** argv) {
	printf("TRACE: enter parse_arguments\n");
	#endif

	// TODO
	// accept a command via stdin
	if (
		argc < 2 ||
		!strncmp(argv[1], "-h", 2) ||
		!strncmp(argv[1], "help", 4)
	) {
		ctx.args.help = true;
		return true;
	}

	if (atexit(trap_exit)) {
		perror("Failed to register exit handler. Exiting...\n");
		return false;
	}

	if (!strncmp(argv[1], "use", 3)) {
		ctx.args.use = true;
		return true;
	if (argc > 1) {

		// is the first arg a call for help?
		if (
			!strncmp(argv[1], "-h", 2) ||
			!strncmp(argv[1], "help", 4)
		) {
			ctx.args.help = true;
			return true;
		}

		// or a call to use?
		if (!strncmp(argv[1], "use", 3)) {
			ctx.args.use = true;
			return parse_nvmrc();
		}

		// or a version?
		ctx.args.version = parse_version(argv[1]);
		return ctx.args.version;
	}

	ctx.args.version = parse_version(argv[1]);
	return ctx.args.version;
	// otherwise, try and read a version from stdin
	ctx.args.version = true;
	return parse_stdin();
}

int dispatch_command(int argc, char** argv) {
int chnode(int argc, char** argv) {
	#ifdef TRACE
	printf("TRACE: enter dispatch_command\n");
	#endif

	if (!parse_arguments(argc, argv)) return EXIT_FAILURE;
	if (!chnode_dir()) return EXIT_FAILURE;

	// TODO
	// dispatch with one variable and function pointers?

	if (!parse_arguments(argc, argv)) {
		#ifdef TRACE
		printf("TRACE: enter enter parse_arguments fail branch\n");
		#endif
		return EXIT_FAILURE;
	}

	if (ctx.args.help) {
		#ifdef TRACE
		printf("TRACE: enter help branch\n");
		#endif
		show_intro();
		show_usage();
		show_current();
		return EXIT_SUCCESS;
	}

	if (ctx.args.use) {
		// TODO
		// from stdin/argv
		return EXIT_SUCCESS;
	}

	if (ctx.args.version) {
		#ifdef TRACE
		printf("TRACE: enter version branch\n");
		#endif
		printf(
			"Using v%s.%s.%s...\n",
			ctx.version.major,


@@ 696,6 806,7 @@ int dispatch_command(int argc, char** argv) {
		);

		if (
			chnode_dir() &&
			version_dir() &&
			release_dir() &&
			download_and_verify() &&


@@ 706,5 817,9 @@ int dispatch_command(int argc, char** argv) {
		) return EXIT_SUCCESS;
	}

	#ifdef TRACE
	printf("TRACE: enter failure branch\n");
	#endif

	return EXIT_FAILURE;
}