From 2f8fbbccd0cc11db0c151a621236247e4dd3b8e3 Mon Sep 17 00:00:00 2001 From: EuAndreh Date: Wed, 1 Sep 2021 10:41:36 -0300 Subject: mv src/remembering-c.c src/remembering.c --- src/remembering.c | 395 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 395 insertions(+) create mode 100644 src/remembering.c (limited to 'src/remembering.c') diff --git a/src/remembering.c b/src/remembering.c new file mode 100644 index 0000000..108d246 --- /dev/null +++ b/src/remembering.c @@ -0,0 +1,395 @@ +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif + +#include +#include +#include +#include +#include +#include + + +#ifdef TEST +#include "unit-test.h" +#include +#include +#endif + + +const int EXIT_ERROR = 1; +const int EXIT_USAGE = 2; + +static int usage(FILE *stream) { + const char *const msg = "Usage: remembering -p PROFILE -c COMMAND\n"; + if (fprintf(stream, msg) < 0) { + perror("usage() - fprintf()"); + return -1; + } + return 0; +} + +static int help(FILE *stream) { + const char *const msg = + "Options:\n" + " -p, PROFILE profile to be used for gathering and storing data\n" + " -c, COMMAND commant to be run, reading from STDIN, writing to STDOUT\n" + " -h, --help show this help\n" + " -V, --version print the version number\n" + "\nSee \"man remembering\" for more information\n"; + if (fprintf(stream, msg) < 0) { + perror("help() - fprint()"); + return -1; + } + return 0; +} + +static int version(FILE *stream) { + const char *const msg = "remembering-" VERSION " " DATE "\n"; + if (fprintf(stream, msg) < 0) { + perror("version() - fprintf()"); + return -1; + } + return 0; +} + +static int missing(FILE *stream, const char *const argument) { + const char *const msg = "Missing option: %s\n"; + if (fprintf(stream, msg, argument) < 0) { + perror("missing() - fprintf()"); + return -1; + } + return 0; +} + +static char *get_profile_file(const char *const profile, FILE *stream) { + char *home = getenv("XDG_DATA_HOME"); + if (!home || (strcmp(home, "") == 0)) { + if (!(home = getenv("HOME"))) { + fprintf(stream, "Unable to get $XDG_DATA_HOME or $HOME environment variables\n"); + return NULL; + } + const char *const suffix = "/.local/share/remembering"; + const size_t home_suffix_size = + strlen(home) + + strlen(suffix) + + sizeof(char); + char *const home_with_suffix = malloc(home_suffix_size); + if (!home_with_suffix) { + perror("get_profile_file() - malloc() - home_with_suffix"); + return NULL; + } + strcpy(home_with_suffix, home); + strcat(home_with_suffix, suffix); + home = home_with_suffix; + } else { + if (!(home = strdup(home))) { + perror("get_profile_file() - strdup()"); + return NULL; + } + } + + size_t size = + strlen(home) + + strlen("/") + + strlen(profile) + + sizeof(char); + char *const profile_file = malloc(size); + if (!profile_file) { + perror("get_profile_file() - malloc() - profile_file"); + free(home); + return NULL; + } + strcpy(profile_file, home); + strcat(profile_file, "/"); + strcat(profile_file, profile); + + free(home); + return profile_file; +} + +#ifdef TEST +static void test_get_profile_file() { + #define EXPECTED_FMT "\nexpected: %s\ngot: %s\n" + FILE *f = fopen("/dev/null", "w"); + assert(f); + + { + testing("get_profile_file() when $XDG_DATA_HOME is *unset*"); + const char *const previous_xdg_data_home = getenv("XDG_DATA_HOME"); + unsetenv("XDG_DATA_HOME"); + + const char *const HOME = getenv("HOME"); + const char *const SUFFIX = "/.local/share/remembering/"; + const char *const A_PROFILE = "A-PROFILE-NAME"; + assert(HOME); + + const size_t expected_size = + strlen(HOME) + + strlen(SUFFIX) + + strlen(A_PROFILE) + + sizeof(char); + char *const expected = malloc(expected_size); + assert(expected); + strcpy(expected, HOME); + strcat(expected, SUFFIX); + strcat(expected, A_PROFILE); + + char *const profile_file = get_profile_file(A_PROFILE, f); + assert(profile_file); + assertf( + strcmp(profile_file, expected) == 0, + EXPECTED_FMT, + expected, + profile_file + ); + + free(profile_file); + free(expected); + if (previous_xdg_data_home) { + setenv("XDG_DATA_HOME", previous_xdg_data_home, true); + } + test_ok(); + } + { + testing("get_profile_file() when $XDG_DATA_HOME is *empty*"); + const char *const previous_xdg_data_home = getenv("XDG_DATA_HOME"); + setenv("XDG_DATA_HOME", "", true); + + const char *const HOME = getenv("HOME"); + const char *const SUFFIX = "/.local/share/remembering/"; + const char *const A_PROFILE = "ANOTHER-PROFILE-NAME"; + assert(HOME); + + const size_t expected_size = + strlen(HOME) + + strlen(SUFFIX) + + strlen(A_PROFILE) + + sizeof(char); + char *const expected = malloc(expected_size); + assert(expected); + strcpy(expected, HOME); + strcat(expected, SUFFIX); + strcat(expected, A_PROFILE); + + char *const profile_file = get_profile_file(A_PROFILE, f); + assert(profile_file); + assertf( + strcmp(profile_file, expected) == 0, + EXPECTED_FMT, + expected, + profile_file + ); + + free(profile_file); + free(expected); + if (previous_xdg_data_home) { + setenv("XDG_DATA_HOME", previous_xdg_data_home, true); + } else { + unsetenv("XDG_DATA_HOME"); + } + test_ok(); + } + { + testing("get_profile_file() when $XDG_DATA_HOME is *set*"); + const char *const previous_xdg_data_home = getenv("XDG_DATA_HOME"); + + const char *const XDG_DATA_HOME = "/a/custom/path"; + const char *const A_PROFILE = "YET-ANOTHER-PROFILE-NAME"; + const char *const EXPECTED = "/a/custom/path/YET-ANOTHER-PROFILE-NAME"; + + setenv("XDG_DATA_HOME", XDG_DATA_HOME, true); + + char *const profile_file = get_profile_file(A_PROFILE, f); + assert(profile_file); + assertf( + strcmp(profile_file, EXPECTED) == 0, + EXPECTED_FMT, + EXPECTED, + profile_file + ); + + free(profile_file); + if (previous_xdg_data_home) { + setenv("XDG_DATA_HOME", previous_xdg_data_home, true); + } else { + unsetenv("XDG_DATA_HOME"); + } + test_ok(); + } + { + testing("get_profile_file() when $HOME is *unset*"); + const char *const previous_home = getenv("HOME"); + const char *const previous_xdg_data_home = getenv("XDG_DATA_HOME"); + unsetenv("XDG_DATA_HOME"); + unsetenv("HOME"); + + assert(get_profile_file("ANY-PROFILE", f) == NULL); + + setenv("HOME", previous_home, true); + if (previous_xdg_data_home) { + setenv("XDG_DATA_HOME", previous_xdg_data_home, true); + } + test_ok(); + } + + fclose(f); +} +#endif + +static int mkdir_p(const char *const path, mode_t mode) { + struct stat s; + int ret; + + if (stat(path, &s) == 0 && S_ISDIR(s.st_mode)) { + return 0; + } + + char *const path_dup = strdup(path); + if (!path_dup) { + perror("mkdir_p() - strdup()"); + return -1; + } + + const char *const parent = dirname(path_dup); + if ((ret = mkdir_p(parent, mode))) { + free(path_dup); + return ret; + } + + free(path_dup); + return mkdir(path, mode); +} + +#ifdef TEST +static void test_mkdir_p() { + { + testing("mkdir_p() with an existing directory is a noop"); + struct stat s; + assert(stat("/tmp", &s) == 0 && S_ISDIR(s.st_mode)); + assert(mkdir_p("/tmp", S_IRWXU | S_IRWXG | S_IRWXO) == 0); + assert(stat("/tmp", &s) == 0 && S_ISDIR(s.st_mode)); + test_ok(); + } + { + testing("mkdir_p() with a new directory creates it"); + struct stat s; + const char *const SUBDIRECTORY_SUFFIX = "/a/b/c/d/e"; + char template[] = "/tmp/remembering.XXXXXX"; + const char *const prefix = mkdtemp(template); + assert(prefix); + + assert(stat(prefix, &s) == 0 && S_ISDIR(s.st_mode)); + + const size_t subdirectory_size = + strlen(prefix) + + strlen(SUBDIRECTORY_SUFFIX) + + sizeof(char); + char *const subdirectory = malloc(subdirectory_size); + assert(subdirectory); + strcpy(subdirectory, prefix); + strcat(subdirectory, SUBDIRECTORY_SUFFIX); + + assert(stat(subdirectory, &s) == -1 && errno == ENOENT); + assert(mkdir_p(subdirectory, S_IRWXU | S_IRWXG | S_IRWXO) == 0); + assert(stat(subdirectory, &s) == 0 && S_ISDIR(s.st_mode)); + + free(subdirectory); + test_ok(); + } +} +#endif + +#ifdef TEST +static void unit_tests() { + test_get_profile_file(); + test_mkdir_p(); +} +#endif + +int main(int argc, char *argv[]) { +#ifdef TEST + unit_tests(); + return EXIT_SUCCESS; +#endif + + for (int i = 0; i < argc; i++) { + if (strcmp("--", argv[i]) == 0) { + break; + } else if (strcmp("--help", argv[i]) == 0) { + if (usage(stdout)) return EXIT_ERROR; + if (help(stdout)) return EXIT_ERROR; + return EXIT_SUCCESS; + } else if (strcmp("--version", argv[i]) == 0) { + if (version(stdout)) return EXIT_ERROR; + return EXIT_SUCCESS; + } + } + + int option; + const char *profile = NULL; + const char *command = NULL; + while ((option = getopt(argc, argv, "p:c:hV")) != -1) { + switch (option) { + case 'p': + profile = optarg; + break; + case 'c': + command = optarg; + break; + case 'h': + if (usage(stdout)) return EXIT_ERROR; + if (help(stdout)) return EXIT_ERROR; + return EXIT_SUCCESS; + case 'V': + if (version(stdout)) return EXIT_ERROR; + return EXIT_SUCCESS; + default: + if (usage(stderr)) return EXIT_ERROR; + return EXIT_USAGE; + } + } + + if (!profile) { + if (missing(stderr, "-p PROFILE")) return EXIT_ERROR; + if (usage(stderr)) return EXIT_ERROR; + return EXIT_USAGE; + } + + if (!command) { + if (missing(stderr, "-c COMMAND")) return EXIT_ERROR; + if (usage(stderr)) return EXIT_ERROR; + return EXIT_USAGE; + } + + /* End getopt() */ + + + int ret = EXIT_SUCCESS; + + char *profile_file = NULL; + if (!(profile_file = get_profile_file(profile, stderr))) { + ret = EXIT_ERROR; + goto cleanup; + } + + char *profile_file_dup = NULL; + if (!(profile_file_dup = strdup(profile_file))) { + perror("main() - strdup()"); + ret = EXIT_ERROR; + goto cleanup; + } + + if ((ret = mkdir_p(dirname(profile_file_dup), S_IRWXU | S_IRWXG | S_IRWXO))) { + goto cleanup; + } + + printf("profile: %s\ncommand: %s\n", profile, command); + printf("profile_file: %s\n", profile_file); + + +cleanup: + free(profile_file_dup); + free(profile_file); + return ret; +} -- cgit v1.2.3