/* * Copyright (c) 2026 Benjamin Linskey * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "pathnames.h" #include "rogue.h" #define XDG_DIR "rogue" #define DOTFILE_DIR ".rogue" #define SCORE_FILE "scores" static char *get_prog_dir_path(void); static char *get_score_path(void); static int ensure_prog_dir(void); /* * Opens the score file for reading and writing and returns a file pointer. * Returns NULL on error. */ FILE * open_score_file(void) { if (ensure_prog_dir() == -1) { printf("Failed to ensure directory\n"); return NULL; } char *path = get_score_path(); FILE *fp = fopen(path, "r+"); if (!fp && errno == ENOENT) { fp = fopen(path, "w+"); } free(path); if (!fp) { printf("Failed to open file %s\n", path); } return fp; } /* * Returns the path to the program data directory. The caller is responsible * for freeing the returned memory. */ static char * get_prog_dir_path(void) { /* * Use $XDG_DATA_HOME if defined, or the user's home directory * otherwise. */ char *data_dir = getenv("XDG_DATA_HOME"); char *dir; if (data_dir) { dir = XDG_DIR; } else { dir = DOTFILE_DIR; data_dir = getenv("HOME"); if (!data_dir) { data_dir = getpwuid(getuid())->pw_dir; } } size_t len = strlen(data_dir) + strlen(dir) + 1; bool add_slash = false; if (data_dir[strlen(data_dir) - 1] != '/') { len += 1; add_slash = true; } char *buf = malloc(len); strlcpy(buf, data_dir, len); if (add_slash) { strlcat(buf, "/", len); } strlcat(buf, dir, len); return buf; } /* * Returns the path to the score file. The caller is repsonsible for freeing * the returned memory. */ static char * get_score_path(void) { char *dir = get_prog_dir_path(); // Account for slash after directory and trailing NULL. size_t len = strlen(dir) + strlen(SCORE_FILE) + 2; char *buf = malloc(len); strlcpy(buf, dir, len); strlcat(buf, "/", len); strlcat(buf, SCORE_FILE, len); free(dir); return buf; } /* * Ensures that the program data directory exists. Returns 0 on success or -1 * if an error occurs. */ static int ensure_prog_dir(void) { int ret; char *path = get_prog_dir_path(); struct stat s = {0}; int err = stat(path, &s); // If file does not exist, create the directory. if (err && errno == ENOENT) { ret = mkdir(path, 0755); goto cleanup; } // If file does exist and stat() returned an error, bail out. if (err) { ret = err; goto cleanup; } // If file is a directory, we don't need to do anything. if (s.st_mode & S_IFDIR) { ret = 0; goto cleanup; } // File exists and isn't a directory, so return an error. ret = -1; goto cleanup; cleanup: free(path); return ret; }