diff --git a/landerctl b/landerctl deleted file mode 100755 index 28f586f..0000000 --- a/landerctl +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env sh - -API_KEY=test -URL=http://localhost:18080 - -if [[ "$2" != '-' ]]; then - filename="$2" - content_type="$(file --mime-type --brief $2)" -fi - - -if [ "$1" = g ]; then - exec curl -is "$URL/$2" | - sed -En 's/^[lL]ocation: (.*)/\1/p' - -elif [ "$1" = s ] || [ "$1" = sl ]; then - exec curl \ - --fail \ - -w "${URL}%header{location}" \ - -XPOST \ - -d "$2" \ - -H "X-Api-Key: $API_KEY" \ - "$URL/$1/$3" - -elif [ "$1" = p ] || [ "$1" = pl ]; then - exec curl \ - --fail \ - -w "${URL}%header{location}" \ - -XPOST \ - -H "X-Api-Key: $API_KEY" \ - -H "X-Lander-Filename: ${filename}" \ - --data-binary @"$2" \ - "$URL/$1/$3" - -elif [ "$1" = f ] || [ "$1" = fl ]; then - exec curl \ - --fail \ - -v \ - -w "${URL}%header{location}" \ - -XPOST \ - -H "X-Api-Key: $API_KEY" \ - -H "X-Lander-Content-Type: ${content_type}" \ - -H "X-Lander-Filename: ${filename}" \ - -T "$2" \ - "$URL/$1/$3" - -elif [ "$1" = d ]; then - exec curl \ - --fail \ - -XDELETE \ - -H "X-Api-Key: $API_KEY" \ - "$URL/$2" -fi diff --git a/landerctl/.landerrc b/landerctl/.landerrc new file mode 100644 index 0000000..db525c7 --- /dev/null +++ b/landerctl/.landerrc @@ -0,0 +1 @@ +api_key = test diff --git a/landerctl/Makefile b/landerctl/Makefile new file mode 100644 index 0000000..72b8239 --- /dev/null +++ b/landerctl/Makefile @@ -0,0 +1,120 @@ +# https://spin.atomicobject.com/2016/08/26/makefile-c-projects/ was a great +# base for this Makefile + +-include config.mk + +export CFLAGS +export LDFLAGS + +BIN := $(BUILD_DIR)/$(BIN_FILENAME) + +SRCS != find '$(SRC_DIR)' -iname '*.c' + +SRCS_H != find include -iname '*.h' + +OBJS := $(SRCS:%=$(BUILD_DIR)/%.o) $(SRCS_THIRDPARTY:%=$(BUILD_DIR)/%.o) +DEPS := $(SRCS:%=$(BUILD_DIR)/%.d) + +_CFLAGS := $(addprefix -I,$(INC_DIRS)) $(CFLAGS) -Wall -Wextra -DLANDER_VERSION=\"$(VERSION)\" +_LDFLAGS := $(addprefix -L,$(LIB_DIRS)) $(addprefix -l,$(LIBS)) $(LDFLAGS) + +.PHONY: all +all: $(BIN) + + +# =====COMPILATION===== +# Utility used by the CI to lint +.PHONY: objs +objs: $(OBJS) + +$(BIN): $(OBJS) + $(CC) -o $@ $(OBJS) $(_LDFLAGS) + +$(BUILD_DIR)/$(SRC_DIR)/%.c.o: $(SRC_DIR)/%.c + mkdir -p $(dir $@) + $(CC) $(_CFLAGS) -c $< -o $@ + +$(BUILD_DIR)/$(THIRDPARTY_DIR)/%.c.o: $(THIRDPARTY_DIR)/%.c + mkdir -p $(dir $@) + $(CC) $(_CFLAGS) -c $< -o $@ + +# =====TESTING===== +.PHONY: run +run: $(BIN) + LANDER_API_KEY=test \ + LANDER_DATA_DIR=data \ + '$(BUILD_DIR)/$(BIN_FILENAME)' + +.PHONY: valgrind +valgrind: $(BIN) + LANDER_API_KEY=test \ + LANDER_DATA_DIR=data \ + valgrind '$(BUILD_DIR)/$(BIN_FILENAME)' + +.PHONY: test +test: $(TARGETS_TEST) + +.PHONY: test-mem +test-mem: $(TARGETS_MEM_TEST) + +.PHONY: $(TARGETS_TEST) +$(TARGETS_TEST): test-%: % + ./$^ + +.PHONY: $(TARGETS_MEM_TEST) +$(TARGETS_MEM_TEST): test-mem-%: % + valgrind --tool=memcheck --error-exitcode=1 --track-origins=yes --leak-check=full ./$^ + +.PHONY: build-test +build-test: $(BINS_TEST) + +$(BINS_TEST): %: %.c.o + $(CC) \ + $^ -o $@ + +# Along with the include directory, each test includes $(TEST_DIR) (which +# contains the acutest.h header file), and the src directory of the module it's +# testing. This allows tests to access internal methods, which aren't publicly +# exposed. +$(BUILD_DIR)/$(TEST_DIR)/%.c.o: $(TEST_DIR)/%.c + mkdir -p $(dir $@) + $(CC) $(_CFLAGS) -I$(TEST_DIR) \ + -I$(dir $(@:$(BUILD_DIR)/$(TEST_DIR)/%=$(SRC_DIR)/%)) \ + -c $< -o $@ + + +# =====MAINTENANCE===== +.PHONY: lint +lint: + clang-format -n --Werror $(SRCS) $(SRCS_H) $(SRCS_H_INTERNAL) + +.PHONY: fmt +fmt: + clang-format -i $(SRCS) $(SRCS_H) $(SRCS_H_INTERNAL) + +.PHONY: check +check: + mkdir -p $(BUILD_DIR)/cppcheck + cppcheck \ + $(addprefix -I,$(INC_DIRS)) \ + --cppcheck-build-dir=$(BUILD_DIR)/cppcheck \ + --project=compile_commands.json \ + --error-exitcode=1 \ + --enable=warning,style \ + --inline-suppr \ + --check-level=exhaustive \ + --quiet \ + -j$(shell nproc) + +.PHONY: clean +clean: + rm -rf $(BUILD_DIR) + +.PHONY: bear +bear: clean + bear -- make + bear --append -- make build-test + + +# Make make aware of the .d files +-include $(DEPS) diff --git a/landerctl/config.mk b/landerctl/config.mk new file mode 100644 index 0000000..611faaf --- /dev/null +++ b/landerctl/config.mk @@ -0,0 +1,22 @@ +VERSION := 0.2.0 + +BIN_FILENAME = landerctl + +BUILD_DIR = build +SRC_DIR = src +TEST_DIR = test + +INC_DIRS = include +LIBS = magic curl +LIB_DIRS = + +# -MMD: generate a .d file for every source file. This file can be imported by +# make and makes make aware that a header file has been changed, ensuring an +# object file is also recompiled if only a header is changed. +# -MP: generate a dummy target for every header file (according to the docs it +# prevents some errors when removing header files) +CFLAGS = -MMD -MP -g + +# When compiling release builds, these flags are better +# CLAGS = -O3 +# LDFLAGS = -flto diff --git a/landerctl/include/landerctl.h b/landerctl/include/landerctl.h new file mode 100644 index 0000000..59a6301 --- /dev/null +++ b/landerctl/include/landerctl.h @@ -0,0 +1,23 @@ +#ifndef LANDERCTL +#define LANDERCTL + +typedef struct landerctl_cfg { + const char *api_key; +} landerctl_cfg; + +typedef enum landerctl_cfg_err { + landerctl_cfg_err_ok = 0, + landerctl_cfg_err_not_found, + landerctl_cfg_err_invalid, + landerctl_cfg_err_incomplete, +} landerctl_cfg_err; + +/** + * Try to parse the required config arguments from the config file + * + * @param out config to write values to. Existing values are overwritten + * @param path path to config file + */ +landerctl_cfg_err landerctl_cfg_parse(landerctl_cfg *out, const char *path); + +#endif diff --git a/landerctl/src/cfg_parse.c b/landerctl/src/cfg_parse.c new file mode 100644 index 0000000..4a681dc --- /dev/null +++ b/landerctl/src/cfg_parse.c @@ -0,0 +1,59 @@ +#include +#include +#include +#include + +#include "landerctl.h" + +static const char cfg_line_regex_expr[] = "^([^ ]+) *= *([^ ]+)$"; + +landerctl_cfg_err landerctl_cfg_parse(landerctl_cfg *out, const char *path) { + FILE *f = fopen(path, "r"); + + if (f == NULL) { + return landerctl_cfg_err_not_found; + } + + regex_t cfg_line_regex; + regcomp(&cfg_line_regex, cfg_line_regex_expr, REG_EXTENDED); + + // Accept lines of at most 256 lines + char line[256]; + landerctl_cfg_err res = landerctl_cfg_err_incomplete; + + while (fgets(line, sizeof(line), f) != NULL) { + // Last character might be a newline + size_t len = strlen(line); + + if (line[len - 1] == '\n') { + line[len - 1] = '\0'; + } + + regmatch_t reg_groups[3]; + + if (regexec(&cfg_line_regex, line, 3, reg_groups, 0) != 0) { + res = landerctl_cfg_err_not_found; + + break; + } + + // api_key is currently the only value we parse + int key_len = reg_groups[1].rm_eo - reg_groups[1].rm_so; + + if ((strlen("api_key") == key_len) && + (strncmp("api_key", &line[reg_groups[1].rm_so], key_len) == 0)) { + int val_len = reg_groups[2].rm_eo - reg_groups[2].rm_so; + char *buf = malloc(val_len + 1); + strncpy(buf, &line[reg_groups[2].rm_so], val_len); + + out->api_key = buf; + + res = landerctl_cfg_err_ok; + break; + } + } + + fclose(f); + + return res; +} diff --git a/landerctl/src/main.c b/landerctl/src/main.c new file mode 100644 index 0000000..3dd0dae --- /dev/null +++ b/landerctl/src/main.c @@ -0,0 +1,87 @@ +#include +#include +#include +#include + +#include +#include + +#include "landerctl.h" + +const char default_cfg_path[] = ".landerrc"; + +int main(int argc, char **argv) { + landerctl_cfg cfg; + char *err_msg = NULL; + + switch (landerctl_cfg_parse(&cfg, default_cfg_path)) { + case landerctl_cfg_err_ok: + break; + case landerctl_cfg_err_not_found: + err_msg = "Config file not found"; + break; + case landerctl_cfg_err_invalid: + err_msg = "Invalid config file"; + break; + case landerctl_cfg_err_incomplete: + err_msg = "Incomplete config file"; + break; + } + + if (err_msg != NULL) { + fprintf(stderr, "%s\n", err_msg); + exit(1); + } + + /* struct stat sb; */ + + /* stat(argv[1], &sb); */ + + /* printf("file size: %lu\n", sb.st_size); */ + + /* FILE *f = fopen(argv[1], "rb"); */ + + /* if (f == NULL) { */ + /* printf("Couldn't open file.\n"); */ + /* exit(1); */ + /* } */ + + /* curl_global_init(CURL_GLOBAL_ALL); */ + + /* CURL *curl = curl_easy_init(); */ + + /* if (curl == NULL) { */ + /* exit(1); */ + /* } */ + + /* curl_easy_setopt(curl, CURLOPT_URL, "http://localhost:18080/f/"); */ + /* curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); */ + /* curl_easy_setopt(curl, CURLOPT_READDATA, f); */ + + /* curl_off_t file_size = sb.st_size; */ + /* /1* curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, file_size); *1/ */ + /* curl_easy_setopt(curl, CURLOPT_POST, 1L); */ + /* curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, file_size); */ + + /* magic_t cookie = magic_open(MAGIC_MIME_TYPE); */ + /* magic_load(cookie, NULL); */ + /* const char *mime_type = magic_file(cookie, argv[1]); */ + + /* char content_type_header[strlen(mime_type) + 24]; */ + /* sprintf(content_type_header, "X-Lander-Content-Type: %s", mime_type); */ + + /* char content_length_header[32]; */ + /* sprintf(content_length_header, "Content-Length: %lu", sb.st_size); */ + + /* struct curl_slist *list = NULL; */ + /* list = curl_slist_append(list, content_type_header); */ + /* list = curl_slist_append(list, content_length_header); */ + /* list = curl_slist_append(list, "X-Api-Key: test"); */ + + /* curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); */ + + /* curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); */ + /* curl_easy_perform(curl); */ + + /* curl_slist_free_all(list); */ +}