From c16e79b612eb8e061a4bd0b5f187c37a036fc403 Mon Sep 17 00:00:00 2001 From: Simon Ochsenreither Date: Fri, 27 Oct 2017 23:15:06 +0200 Subject: [PATCH] startup: look for nanorc and history files also in the XDG directories When not finding a .nanorc file in the user's home directory, nano will look for a nanorc file in $XDG_CONFIG_HOME and in the ~/.config/nano/ fallback directory. And when not finding a .nano/ subdir in the user's home directory, nano will look for (or create) the history files in $XDG_DATA_HOME or in the ~/.local/share/nano/ fallback directory. This is a partial implementation of the XDG Base Directory Specification: https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html, for the purpose of reducing the clutter in a user's home directory, and to make it easier to back up just the configuration files. Signed-off-by: Simon Ochsenreither Signed-off-by: Benno Schulenberg --- src/global.c | 2 ++ src/history.c | 88 ++++++++++++++++++++++++++++----------------------- src/nano.c | 3 +- src/proto.h | 4 ++- src/rcfile.c | 37 ++++++++++++++++------ src/utils.c | 12 +++++++ 6 files changed, 94 insertions(+), 52 deletions(-) diff --git a/src/global.c b/src/global.c index 32096605..facda319 100644 --- a/src/global.c +++ b/src/global.c @@ -242,6 +242,8 @@ int interface_color_pair[] = {0}; char *homedir = NULL; /* The user's home directory, from $HOME or /etc/passwd. */ +char *statedir = NULL; + /* The directory for nano's history files. */ char *rcfile_with_errors = NULL; /* The first nanorc file, if any, that produced warnings. */ diff --git a/src/history.c b/src/history.c index 1a9f1225..d56cc761 100644 --- a/src/history.c +++ b/src/history.c @@ -25,6 +25,19 @@ #include #ifdef ENABLE_HISTORIES + +#ifndef XDG_DATA_FALLBACK +#define XDG_DATA_FALLBACK "/.local/share" +#endif + +#ifndef SEARCH_HISTORY +#define SEARCH_HISTORY "search_history" +#endif + +#ifndef POSITION_HISTORY +#define POSITION_HISTORY "filepos_history" +#endif + static bool history_changed = FALSE; /* Whether any of the history lists has changed. */ @@ -216,29 +229,6 @@ char *get_history_completion(filestruct **h, char *s, size_t len) } #endif /* ENSABLE_TABCOMP */ -/* Return a dynamically-allocated path that is the concatenation of the - * user's home directory and the given name. */ -char *construct_filename(const char *name) -{ - size_t homelen = strlen(homedir); - char *joined = charalloc(homelen + strlen(name) + 1); - - strcpy(joined, homedir); - strcpy(joined + homelen, name); - - return joined; -} - -char *histfilename(void) -{ - return construct_filename("/.nano/search_history"); -} - -char *poshistfilename(void) -{ - return construct_filename("/.nano/filepos_history"); -} - void history_error(const char *msg, ...) { va_list ap; @@ -252,38 +242,56 @@ void history_error(const char *msg, ...) ; } -/* Check whether the ~/.nano subdirectory for history files exists. Return - * TRUE if it exists or was successfully created, and FALSE otherwise. */ -bool have_dotnano(void) +/* Check whether we have or could make a directory for history files. */ +bool have_statedir(void) { - bool retval = TRUE; struct stat dirstat; - char *nanodir = construct_filename("/.nano"); + char *xdgdatadir; - if (stat(nanodir, &dirstat) == -1) { - if (mkdir(nanodir, S_IRWXU | S_IRWXG | S_IRWXO) == -1) { + get_homedir(); + + if (homedir != NULL) { + statedir = concatenate(homedir, "/.nano/"); + + if (stat(statedir, &dirstat) != 0 && S_ISDIR(dirstat.st_mode)) + return TRUE; + } + + free(statedir); + xdgdatadir = getenv("XDG_DATA_HOME"); + + if (homedir == NULL && xdgdatadir == NULL) + return FALSE; + + if (xdgdatadir != NULL) { + statedir = concatenate(xdgdatadir, "/nano/"); + free(xdgdatadir); + } else + statedir = concatenate(homedir, XDG_DATA_FALLBACK "/nano/"); + + if (stat(statedir, &dirstat) == -1) { + if (mkdir(statedir, S_IRWXU | S_IRWXG | S_IRWXO) == -1) { history_error(N_("Unable to create directory %s: %s\n" "It is required for saving/loading " "search history or cursor positions.\n"), - nanodir, strerror(errno)); - retval = FALSE; + statedir, strerror(errno)); + return FALSE; } } else if (!S_ISDIR(dirstat.st_mode)) { history_error(N_("Path %s is not a directory and needs to be.\n" "Nano will be unable to load or save " "search history or cursor positions.\n"), - nanodir); - retval = FALSE; + statedir); + return FALSE; } - free(nanodir); - return retval; + return TRUE; } /* Load the histories for Search and Replace and Execute Command. */ void load_history(void) { - char *histname = histfilename(); + char *histname = concatenate(statedir, SEARCH_HISTORY); FILE *hisfile = fopen(histname, "rb"); if (hisfile == NULL) { @@ -356,7 +364,7 @@ void save_history(void) if (!history_changed) return; - histname = histfilename(); + histname = concatenate(statedir, SEARCH_HISTORY); hisfile = fopen(histname, "wb"); if (hisfile == NULL) @@ -381,7 +389,7 @@ void save_history(void) /* Load the recorded cursor positions for files that were edited. */ void load_poshistory(void) { - char *poshist = poshistfilename(); + char *poshist = concatenate(statedir, POSITION_HISTORY); FILE *hisfile = fopen(poshist, "rb"); if (hisfile == NULL) { @@ -447,7 +455,7 @@ void load_poshistory(void) /* Save the recorded cursor positions for files that were edited. */ void save_poshistory(void) { - char *poshist = poshistfilename(); + char *poshist = concatenate(statedir, POSITION_HISTORY); poshiststruct *posptr; FILE *hisfile = fopen(poshist, "wb"); diff --git a/src/nano.c b/src/nano.c index 8ef381e8..15d8d546 100644 --- a/src/nano.c +++ b/src/nano.c @@ -2357,8 +2357,7 @@ int main(int argc, char **argv) /* If we need any of the history files, verify that the user's home * directory and its .nano subdirctory exist. */ if (ISSET(HISTORYLOG) || ISSET(POS_HISTORY)) { - get_homedir(); - if (homedir == NULL || !have_dotnano()) { + if (!have_statedir()) { UNSET(HISTORYLOG); UNSET(POS_HISTORY); } diff --git a/src/proto.h b/src/proto.h index 559f1521..538b2b55 100644 --- a/src/proto.h +++ b/src/proto.h @@ -181,6 +181,7 @@ extern char* specified_color_combo[NUMBER_OF_ELEMENTS]; extern int interface_color_pair[NUMBER_OF_ELEMENTS]; extern char *homedir; +extern char *statedir; extern char *rcfile_with_errors; typedef void (*functionptrtype)(void); @@ -365,7 +366,7 @@ void get_history_newer_void(void); #ifdef ENABLE_TABCOMP char *get_history_completion(filestruct **h, char *s, size_t len); #endif -bool have_dotnano(void); +bool have_statedir(void); void load_history(void); void save_history(void); void load_poshistory(void); @@ -581,6 +582,7 @@ void complete_a_word(void); /* All functions in utils.c. */ void get_homedir(void); +char *concatenate(const char *path, const char *name); #ifdef ENABLE_LINENUMBERS int digits(ssize_t n); #endif diff --git a/src/rcfile.c b/src/rcfile.c index a2d8f834..c43a286f 100644 --- a/src/rcfile.c +++ b/src/rcfile.c @@ -31,7 +31,7 @@ #ifdef ENABLE_NANORC #ifndef RCFILE_NAME -#define RCFILE_NAME ".nanorc" +#define RCFILE_NAME "nanorc" #endif static const rcoption rcopts[] = { @@ -504,11 +504,15 @@ void parse_binding(char *ptr, bool dobind) free(keycopy); } -/* Verify that the given file is not a folder nor a device. */ +/* Verify that the given file exists, is not a folder nor a device. */ bool is_good_file(char *file) { struct stat rcinfo; + /* First check that the file exists and is readable. */ + if (access(file, R_OK) != 0) + return FALSE; + /* If the thing exists, it may not be a directory nor a device. */ if (stat(file, &rcinfo) != -1 && (S_ISDIR(rcinfo.st_mode) || S_ISCHR(rcinfo.st_mode) || S_ISBLK(rcinfo.st_mode))) { @@ -1211,6 +1215,17 @@ void parse_one_nanorc(void) rcfile_error(N_("Error reading %s: %s"), nanorc, strerror(errno)); } +bool have_nanorc(char *path, char *name) +{ + if (path == NULL) + return FALSE; + + free(nanorc); + nanorc = concatenate(path, name); + + return is_good_file(nanorc); +} + /* First read the system-wide rcfile, then the user's rcfile. */ void do_rcfiles(void) { @@ -1228,18 +1243,22 @@ void do_rcfiles(void) get_homedir(); - if (homedir == NULL) - rcfile_error(N_("I can't find my home directory! Wah!")); - else { - nanorc = charealloc(nanorc, strlen(homedir) + strlen(RCFILE_NAME) + 2); - sprintf(nanorc, "%s/%s", homedir, RCFILE_NAME); + char *xdgconfdir = getenv("XDG_CONFIG_HOME"); - /* Process the current user's nanorc. */ + /* Now try the to find a nanorc file in the user's home directory + * or in the XDG configuration directories. */ + if (have_nanorc(homedir, "/." RCFILE_NAME)) parse_one_nanorc(); - } + else if (have_nanorc(xdgconfdir, "/nano/" RCFILE_NAME)) + parse_one_nanorc(); + else if (have_nanorc(homedir, "/.config/nano/" RCFILE_NAME)) + parse_one_nanorc(); + else if (homedir == NULL && xdgconfdir == NULL) + rcfile_error(N_("I can't find my home directory! Wah!")); check_vitals_mapped(); + free(xdgconfdir); free(nanorc); } diff --git a/src/utils.c b/src/utils.c index 42f12540..530dd2cd 100644 --- a/src/utils.c +++ b/src/utils.c @@ -53,6 +53,18 @@ void get_homedir(void) } } +/* Return a copy of the two given strings, welded together. */ +char *concatenate(const char *path, const char *name) +{ + size_t pathlen = strlen(path); + char *joined = charalloc(pathlen + strlen(name) + 1); + + strcpy(joined, path); + strcpy(joined + pathlen, name); + + return joined; +} + #ifdef ENABLE_LINENUMBERS /* Return the number of digits that the given integer n takes up. */ int digits(ssize_t n)