diff --git c/src/history.c w/src/history.c index 3abdca619f..be894e326c 100644 --- c/src/history.c +++ w/src/history.c @@ -24,6 +24,18 @@ #include #include +#ifndef XDG_DATA_FALLBACK +#define XDG_DATA_FALLBACK "/.local/share/nano" +#endif + +#ifndef SEARCH_HISTORY +#define SEARCH_HISTORY "search_history" +#endif + +#ifndef POSITION_HISTORY +#define POSITION_HISTORY "position_history" +#endif + #ifndef DISABLE_HISTORIES static bool history_changed = FALSE; /* Whether any of the history lists has changed. */ @@ -216,9 +228,9 @@ 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) +/* Return a dynamically-allocated path that is the concatenation of the given + path and name. */ +char *construct_filename(const char *path, const char *name) { size_t homelen = strlen(homedir); char *joined = charalloc(homelen + strlen(name) + 1); @@ -229,21 +241,44 @@ char *construct_filename(const char *name) return joined; } -char *histfilename(void) +/* Construct the legacy history filename. + (To be removed in 2020.) */ +char *legacysearchhistfilename(void) { - return construct_filename("/.nano/search_history"); + return construct_filename(homedir, "/.nano/" SEARCH_HISTORY); } -/* Construct the legacy history filename. */ -/* (To be removed in 2018.) */ -char *legacyhistfilename(void) +char *searchhistfilename(void) { - return construct_filename("/.nano_history"); + char *filename; + char *xdgdatadir = getenv("XDG_DATA_HOME"); + if (xdgdatadir != NULL) { + filename = construct_filename(xdgdatadir, "/nano/" SEARCH_HISTORY); + free(xdgdatadir); + } else { + filename = construct_filename(homedir, XDG_DATA_FALLBACK "/" SEARCH_HISTORY); + } + return filename; } -char *poshistfilename(void) +/* Construct the legacy history filename. + (To be removed in 2020.) */ +char *legacypositionhistfilename(void) { - return construct_filename("/.nano/filepos_history"); + return construct_filename(homedir, "/.nano/filepos_history"); +} + +char *positionhistfilename(void) +{ + char *filename; + char *xdgdatadir = getenv("XDG_DATA_HOME"); + if (xdgdatadir != NULL) { + filename = construct_filename(xdgdatadir, "/nano/" POSITION_HISTORY); + free(xdgdatadir); + } else { + filename = construct_filename(homedir, XDG_DATA_FALLBACK "/" POSITION_HISTORY); + } + return filename; } void history_error(const char *msg, ...) @@ -259,63 +294,86 @@ 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) -{ +/* Return TRUE if directory exists or was successfully created, + * and FALSE otherwise. */ +bool create_xdgdata_nano_directory_if_not_existing(char *nanodatadir) { +#ifdef DEBUG + fprintf(stderr, "Checking for nano data directory \"%s\"\n", nanodatadir); +#endif bool retval = TRUE; struct stat dirstat; - char *nanodir = construct_filename("/.nano"); - - if (stat(nanodir, &dirstat) == -1) { - if (mkdir(nanodir, 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; - } + if (stat(nanodatadir, &dirstat) == -1) { + if (mkdir(nanodatadir, 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"), + nanodatadir, strerror(errno)); + retval = 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; + 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"), + nanodatadir); + retval = FALSE; } + return retval; +} - free(nanodir); +/* Check whether the nano data directory (whose location is determined by the + * XDG base dir spec) used for history files exists. + * Return TRUE if it exists or was successfully created, and FALSE otherwise. */ +bool have_or_created_xdgdata_nano(void) +{ + bool retval = TRUE; + char *xdgdatadir = getenv("XDG_DATA_HOME"); + char *nanodatadir; + + if (xdgdatadir != NULL) { + nanodatadir = construct_filename(xdgdatadir, "/nano"); + retval = create_xdgdata_nano_directory_if_not_existing(nanodatadir); + free(xdgdatadir); + } else { + nanodatadir = construct_filename(homedir, XDG_DATA_FALLBACK); + retval = create_xdgdata_nano_directory_if_not_existing(nanodatadir); + } + + free(nanodatadir); return retval; } /* Load the histories for Search and Replace and Execute Command. */ void load_history(void) { - char *histname = histfilename(); - char *legacyhist = legacyhistfilename(); + char *histname = searchhistfilename(); + char *legacyhist = legacysearchhistfilename(); struct stat hstat; FILE *hisfile; /* If there is an old history file, migrate it. */ - /* (To be removed in 2018.) */ + /* (To be removed in 2020.) */ if (stat(legacyhist, &hstat) != -1 && stat(histname, &hstat) == -1) { - if (rename(legacyhist, histname) == -1) - history_error(N_("Detected a legacy nano history file (%s) which I tried to move\n" - "to the preferred location (%s) but encountered an error: %s"), - legacyhist, histname, strerror(errno)); - else - history_error(N_("Detected a legacy nano history file (%s) which I moved\n" - "to the preferred location (%s)\n(see the nano FAQ about this change)"), - legacyhist, histname); + if (rename(legacyhist, histname) == -1) + history_error(N_("Detected a legacy nano history file (%s) which I tried to move\n" + "to the preferred location (%s) but encountered an error: %s"), + legacyhist, histname, strerror(errno)); + else + history_error(N_("Detected a legacy nano history file (%s) which I moved\n" + "to the preferred location (%s)\n(see the nano FAQ about this change)"), + legacyhist, histname); } +#ifdef DEBUG + fprintf(stderr, "Opening search history file \"%s\"\n", histname); +#endif + hisfile = fopen(histname, "rb"); if (hisfile == NULL) { - if (errno != ENOENT) { - /* When reading failed, don't save history when we quit. */ - UNSET(HISTORYLOG); - history_error(N_("Error reading %s: %s"), histname, - strerror(errno)); + if (errno != ENOENT) { + /* When reading failed, don't save history when we quit. */ + UNSET(HISTORYLOG); + history_error(N_("Error reading %s: %s"), histname, strerror(errno)); } } else { /* Load the two history lists -- first the search history, then @@ -326,18 +384,17 @@ void load_history(void) size_t buf_len = 0; ssize_t read; - while ((read = getline(&line, &buf_len, hisfile)) > 0) { - line[--read] = '\0'; - if (read > 0) { - /* Encode any embedded NUL as 0x0A. */ - unsunder(line, read); - update_history(history, line); - } else if (history == &search_history) - history = &replace_history; - else - history = &execute_history; - - } + while ((read = getline(&line, &buf_len, hisfile)) > 0) { + line[--read] = '\0'; + if (read > 0) { + /* Encode any embedded NUL as 0x0A. */ + unsunder(line, read); + update_history(history, line); + } else if (history == &search_history) + history = &replace_history; + else + history = &execute_history; + } fclose(hisfile); free(line); @@ -381,7 +438,7 @@ void save_history(void) if (!history_changed) return; - histname = histfilename(); + histname = searchhistfilename(); hisfile = fopen(histname, "wb"); if (hisfile == NULL) @@ -406,8 +463,29 @@ void save_history(void) /* Load the recorded cursor positions for files that were edited. */ void load_poshistory(void) { - char *poshist = poshistfilename(); - FILE *hisfile = fopen(poshist, "rb"); + char *poshist = positionhistfilename(); + char *legacyposhist = legacypositionhistfilename(); + struct stat hstat; + FILE *hisfile; + + /* If there is an old history file, migrate it. */ + /* (To be removed in 2020.) */ + if (stat(legacyposhist, &hstat) != -1 && stat(poshist, &hstat) == -1) { + if (rename(legacyposhist, poshist) == -1) + history_error(N_("Detected a legacy nano history file (%s) which I tried to move\n" + "to the preferred location (%s) but encountered an error: %s"), + legacyposhist, poshist, strerror(errno)); + else + history_error(N_("Detected a legacy nano history file (%s) which I moved\n" + "to the preferred location (%s)\n(see the nano FAQ about this change)"), + legacyposhist, poshist); + } + +#ifdef DEBUG + fprintf(stderr, "Opening position history file \"%s\"\n", poshist); +#endif + + hisfile = fopen(poshist, "rb"); if (hisfile == NULL) { if (errno != ENOENT) { @@ -467,12 +545,13 @@ void load_poshistory(void) free(line); } free(poshist); + free(legacyposhist); } /* Save the recorded cursor positions for files that were edited. */ void save_poshistory(void) { - char *poshist = poshistfilename(); + char *poshist = positionhistfilename(); poshiststruct *posptr; FILE *hisfile = fopen(poshist, "wb"); diff --git c/src/nano.c w/src/nano.c index 9c53b7294f..8fc671f66c 100644 --- c/src/nano.c +++ w/src/nano.c @@ -2373,7 +2373,7 @@ int main(int argc, char **argv) * directory and its .nano subdirctory exist. */ if (ISSET(HISTORYLOG) || ISSET(POS_HISTORY)) { get_homedir(); - if (homedir == NULL || !have_dotnano()) { + if (homedir == NULL || !have_or_created_xdgdata_nano()) { UNSET(HISTORYLOG); UNSET(POS_HISTORY); } diff --git c/src/proto.h w/src/proto.h index 71c12d4092..d6190f4f25 100644 --- c/src/proto.h +++ w/src/proto.h @@ -366,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_or_created_xdgdata_nano(); void load_history(void); void save_history(void); void load_poshistory(void); diff --git c/src/rcfile.c w/src/rcfile.c index 2988d4fc76..a328da698f 100644 --- c/src/rcfile.c +++ w/src/rcfile.c @@ -34,6 +34,10 @@ #define RCFILE_NAME ".nanorc" #endif +#ifndef XDG_RCFILE_NAME +#define XDG_RCFILE_NAME "config" +#endif + static const rcoption rcopts[] = { {"boldtext", BOLD_TEXT}, #ifdef ENABLE_LINENUMBERS @@ -511,19 +515,27 @@ 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; - - /* 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))) { - rcfile_error(S_ISDIR(rcinfo.st_mode) ? _("\"%s\" is a directory") : - _("\"%s\" is a device file"), file); - return FALSE; - } else - return TRUE; + stat(file, &rcinfo); + + if (access(file, R_OK) != 0) { + /* file does not exist or is not readable */ + return FALSE; + } else if ( + S_ISDIR(rcinfo.st_mode) || + S_ISCHR(rcinfo.st_mode) || + S_ISBLK(rcinfo.st_mode)) { + rcfile_error(S_ISDIR(rcinfo.st_mode) + ? _("\"%s\" is a directory") + : _("\"%s\" is a device file"), file); + /* file is a directory, a char device or a block device */ + return FALSE; + } else { + return TRUE; + } } #ifndef DISABLE_COLOR @@ -1234,16 +1246,36 @@ void do_rcfiles(void) SET(NO_WRAP); #endif - 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); - - /* Process the current user's nanorc. */ - parse_one_nanorc(); + /* Process the current user's nanorc. */ + + /* use $XDG_CONFIG_HOME/nanorc if set */ + char *xdgconfdir = getenv("XDG_CONFIG_HOME"); + if (xdgconfdir != NULL) { + nanorc = charealloc(nanorc, strlen(xdgconfdir) + strlen(XDG_RCFILE_NAME) + 2); + sprintf(nanorc, "%s/%s", xdgconfdir, XDG_RCFILE_NAME); + if(is_good_file(nanorc)) { + parse_one_nanorc(); + } + free(xdgconfdir); + } else { + /* try $HOME/.nanorc */ + get_homedir(); + if (homedir != NULL) { + nanorc = charealloc(nanorc, strlen(homedir) + strlen(RCFILE_NAME) + 2); + sprintf(nanorc, "%s/%s", homedir, RCFILE_NAME); + if(is_good_file(nanorc)) { + parse_one_nanorc(); + } else { + /* try .config/nano/config */ + nanorc = charealloc(nanorc, strlen(homedir) + strlen(XDG_RCFILE_NAME) + 15); + sprintf(nanorc, "%s/.config/nano/%s", homedir, XDG_RCFILE_NAME); + if(is_good_file(nanorc)) { + parse_one_nanorc(); + } + } + } else { + rcfile_error(N_("I can't find my home directory! Wah!")); + } } check_vitals_mapped();