#ifndef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 200809L #endif #include #include #include #include #include #include #ifdef TEST #include "unit-test.h" #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; } strcat(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; } strcat(profile_file, home); strcat(profile_file, "/"); strcat(profile_file, profile); 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); strcat(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); strcat(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)) { printf("ISDIR: %s\n", path); 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); } int main(int argc, char *argv[]) { #ifdef TEST test_get_profile_file(); 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 getopts 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; }