From a27bd650572f1ca13494963edf86bf69a82e5ff5 Mon Sep 17 00:00:00 2001 From: David Lawrence Ramsey Date: Tue, 17 Aug 2004 05:23:38 +0000 Subject: [PATCH] per DB's patch, overhaul the rcfile and history file reading and writing routines to fix a few fundamental problems and limitations; also add getline() and getdelim() equivalents adapted from GNU mailutils 0.5 (and tweaked to better integrate with nano), since the patch uses getline() git-svn-id: svn://svn.savannah.gnu.org/nano/trunk/nano@1900 35c25a1d-7b9e-4130-9fde-d3aeb78583b8 --- ChangeLog | 22 +++++++-- configure.ac | 2 +- src/files.c | 124 ++++++++++++++++++++++++--------------------------- src/global.c | 7 +++ src/nano.c | 2 +- src/nano.h | 12 ++++- src/proto.h | 12 +++++ src/rcfile.c | 50 +++++++++++---------- src/utils.c | 59 ++++++++++++++++++++++++ 9 files changed, 192 insertions(+), 98 deletions(-) diff --git a/ChangeLog b/ChangeLog index e01a4fed..700f5108 100644 --- a/ChangeLog +++ b/ChangeLog @@ -92,6 +92,14 @@ CVS code - fill_flag_used to avoid warnings when compiling with --disable-wrapping, --disable-justify, or a combination of the two. (DLR) + - Overhaul the routines used to read the rcfiles and history + files for efficiency, make them work properly on lines over + 1023 characters long and on lines containing nulls, and make + them properly handle the case where the user's home directory + changes in the middle of a session. New functions + histfilename() and writehist(); changes to + thanks_for_all_the_fish(), load_history(), save_history(), and + do_rcfile(). (David Benbennick) - files.c: get_next_filename() - Tweak for efficiency, and add the ".save" suffix to the file @@ -112,7 +120,7 @@ CVS code - - global.c: shortcut_init() - Fix erroneous #ifdef so that nano compiles with - --disable-justify again. (DLR; found by Mike Frysinger) + --disable-justify again. (DLR, found by Mike Frysinger) - Change the Cancel shortcut in the file browser to an Exit shortcut, to be more compatible with the current version of Pico. (DLR) @@ -186,8 +194,8 @@ CVS code - match their corresponding location in files.c. (DLR) - rcfile.c: rcfile_msg() - - Removed along with the related static int errors, and replaced - with calls to rcfile_error(). (David Benbennick) + - Removed and replaced with calls to rcfile_error(). (David + Benbennick) - Removed the reference to "starting nano" in the statusbar message, as it may be called when we exit if the history file can't be saved. (DLR) @@ -225,9 +233,13 @@ CVS code - - Remove code chacking for n's being less than 0 that will never be run, since n is a size_t and is hence unsigned. (David Benbennick) + ngetdelim(), ngetline() + - New functions equivalent to getdelim() and getline(), which + are both GNU extensions. (DLR, adapted from GNU mailutils + 0.5) - winio.c: get_kbinput() - - Since the only valid values for escapes are 0, 1, and 2, + - Since the only valid values for escapes are 0, 1, and 2, convert it to an int. (DLR) get_control_kbinput() - Fix erroneous debugging statement so that nano compiles with @@ -254,6 +266,8 @@ CVS code - - configure.ac: - Add AC_PROG_LN_S, so that we can portably create symlinks. (DLR) + - Check for getdelim() and getline(), which are both GNU + extensions. (DLR) - nanorc.sample: - Add sample regexes for patch files. (Mike Frysinger) - Various improvements to the "c-file" regexes. Add double, diff --git a/configure.ac b/configure.ac index 196c4feb..3e3d8792 100644 --- a/configure.ac +++ b/configure.ac @@ -290,7 +290,7 @@ AC_MSG_WARN([*** Can not use slang when cross-compiling])), esac], [AC_MSG_RESULT(no)]) dnl Checks for functions -AC_CHECK_FUNCS(snprintf vsnprintf isblank strcasecmp strncasecmp strcasestr strnlen) +AC_CHECK_FUNCS(snprintf vsnprintf isblank strcasecmp strncasecmp strcasestr strnlen getline getdelim) if test "x$ac_cv_func_snprintf" = "xno" -o "xac_cv_func_vsnprintf" = "xno" then AM_PATH_GLIB_2_0(2.0.0,, diff --git a/src/files.c b/src/files.c index bddfba91..36e642ee 100644 --- a/src/files.c +++ b/src/files.c @@ -2914,30 +2914,30 @@ char *do_browse_from(const char *inpath) #endif /* !DISABLE_BROWSER */ #if !defined(NANO_SMALL) && defined(ENABLE_NANORC) -void load_history(void) +/* Return $HOME/.nano_history, or NULL if we can't find the homedir. + * The string is dynamically allocated, and should be freed. */ +char *histfilename(void) { - FILE *hist; - const struct passwd *userage = NULL; - static char *nanohist; - char *buf, *ptr; - char *homenv = getenv("HOME"); - historyheadtype *history = &search_history; + char *nanohist = NULL; + if (homedir != NULL) { + size_t homelen = strlen(homedir); - if (homenv != NULL) { - nanohist = charealloc(nanohist, strlen(homenv) + 15); - sprintf(nanohist, "%s/.nano_history", homenv); - } else { - userage = getpwuid(geteuid()); - endpwent(); - nanohist = charealloc(nanohist, strlen(userage->pw_dir) + 15); - sprintf(nanohist, "%s/.nano_history", userage->pw_dir); + nanohist = charalloc(homelen + 15); + strcpy(nanohist, homedir); + strcpy(nanohist + homelen, "/.nano_history"); } + return nanohist; +} + +void load_history(void) +{ + char *nanohist = histfilename(); /* assume do_rcfile() has reported missing home dir */ + if (nanohist != NULL) { + FILE *hist = fopen(nanohist, "r"); - if (homenv != NULL || userage != NULL) { - hist = fopen(nanohist, "r"); if (hist == NULL) { if (errno != ENOENT) { /* Don't save history when we quit. */ @@ -2947,80 +2947,72 @@ void load_history(void) while (getchar() != '\n') ; } - free(nanohist); } else { - buf = charalloc(1024); - while (fgets(buf, 1023, hist) != 0) { - ptr = buf; - while (*ptr != '\n' && *ptr != '\0' && ptr < buf + 1023) - ptr++; - *ptr = '\0'; - if (strlen(buf)) - update_history(history, buf); - else + historyheadtype *history = &search_history; + char *line = NULL; + size_t buflen = 0; + ssize_t read; + + while ((read = getline(&line, &buflen, hist)) >= 0) { + if (read > 0 && line[read - 1] == '\n') { + read--; + line[read] = '\0'; + } + if (read > 0) { + unsunder(line, read); + update_history(history, line); + } else history = &replace_history; } fclose(hist); - free(buf); - free(nanohist); + free(line); UNSET(HISTORY_CHANGED); } + free(nanohist); + } +} + +bool writehist(FILE *hist, historyheadtype *histhead) +{ + historytype *h; + + /* write oldest first */ + for (h = histhead->tail; h->prev != NULL; h = h->prev) { + size_t len = strlen(h->data); + + sunder(h->data); + if (fwrite(h->data, sizeof(char), len, hist) < len || + putc('\n', hist) == EOF) + return FALSE; } + return TRUE; } /* save histories to ~/.nano_history */ void save_history(void) { - FILE *hist; - const struct passwd *userage = NULL; - char *nanohist = NULL; - char *homenv = getenv("HOME"); - historytype *h; + char *nanohist; /* don't save unchanged or empty histories */ if ((search_history.count == 0 && replace_history.count == 0) || !ISSET(HISTORY_CHANGED) || ISSET(VIEW_MODE)) return; - if (homenv != NULL) { - nanohist = charealloc(nanohist, strlen(homenv) + 15); - sprintf(nanohist, "%s/.nano_history", homenv); - } else { - userage = getpwuid(geteuid()); - endpwent(); - nanohist = charealloc(nanohist, strlen(userage->pw_dir) + 15); - sprintf(nanohist, "%s/.nano_history", userage->pw_dir); - } + nanohist = histfilename(); + + if (nanohist != NULL) { + FILE *hist = fopen(nanohist, "wb"); - if (homenv != NULL || userage != NULL) { - hist = fopen(nanohist, "wb"); if (hist == NULL) rcfile_error(N_("Error writing %s: %s"), nanohist, strerror(errno)); else { /* set rw only by owner for security ?? */ chmod(nanohist, S_IRUSR | S_IWUSR); - /* write oldest first */ - for (h = search_history.tail; h->prev; h = h->prev) { - h->data = charealloc(h->data, strlen(h->data) + 2); - strcat(h->data, "\n"); - if (fputs(h->data, hist) == EOF) { - rcfile_error(N_("Error writing %s: %s"), nanohist, strerror(errno)); - goto come_from; - } - } - if (fputs("\n", hist) == EOF) { + + if (!writehist(hist, &search_history) || + putc('\n', hist) == EOF || + !writehist(hist, &replace_history)) rcfile_error(N_("Error writing %s: %s"), nanohist, strerror(errno)); - goto come_from; - } - for (h = replace_history.tail; h->prev; h = h->prev) { - h->data = charealloc(h->data, strlen(h->data) + 2); - strcat(h->data, "\n"); - if (fputs(h->data, hist) == EOF) { - rcfile_error(N_("Error writing %s: %s"), nanohist, strerror(errno)); - goto come_from; - } - } - come_from: fclose(hist); } free(nanohist); diff --git a/src/global.c b/src/global.c index 79f81765..f2a947ed 100644 --- a/src/global.c +++ b/src/global.c @@ -178,6 +178,10 @@ bool curses_ended = FALSE; /* Indicates to statusbar() to simply * write to stderr, since endwin() has * ended curses mode. */ +#ifdef ENABLE_NANORC +char *homedir = NULL; /* $HOME or from /etc/passwd. */ +#endif + size_t length_of_list(const shortcut *s) { size_t i = 0; @@ -1188,5 +1192,8 @@ void thanks_for_all_the_fish(void) free_history(&search_history); free_history(&replace_history); #endif +#ifdef ENABLE_NANORC + free(homedir); +#endif } #endif /* DEBUG */ diff --git a/src/nano.c b/src/nano.c index 07ca0c16..3826ef26 100644 --- a/src/nano.c +++ b/src/nano.c @@ -3000,7 +3000,7 @@ void terminal_init(void) disable_flow_control(); } -int main(int argc, char *argv[]) +int main(int argc, char **argv) { int optchr; int startline = 0; /* Line to try and start at */ diff --git a/src/nano.h b/src/nano.h index be0a7af7..b145022f 100644 --- a/src/nano.h +++ b/src/nano.h @@ -95,8 +95,8 @@ # endif #endif -/* If no isblank(), strcasecmp(), strncasecmp(), strcasestr(), or - * strnlen(), use the versions we have. */ +/* If no isblank(), strcasecmp(), strncasecmp(), strcasestr(), + * strnlen(), getdelim(), or getline(), use the versions we have. */ #ifndef HAVE_ISBLANK #define isblank is_blank_char #endif @@ -117,6 +117,14 @@ #define strnlen nstrnlen #endif +#ifndef HAVE_GETDELIM +#define getdelim ngetdelim +#endif + +#ifndef HAVE_GETLINE +#define getline ngetline +#endif + /* Assume ERR is defined as -1. To avoid duplicate case values when * some key definitions are missing, we have to set all of these, and * all of the special sentinel values below, to different negative diff --git a/src/proto.h b/src/proto.h index 3ec5e3d9..6f03d74f 100644 --- a/src/proto.h +++ b/src/proto.h @@ -142,6 +142,10 @@ extern historyheadtype replace_history; extern bool curses_ended; +#ifdef ENABLE_NANORC +extern char *homedir; +#endif + /* Functions we want available. */ /* Public functions in color.c */ @@ -224,7 +228,9 @@ char *do_browser(const char *inpath); char *do_browse_from(const char *inpath); #endif #if !defined(NANO_SMALL) && defined(ENABLE_NANORC) +char *histfilename(void); void load_history(void); +bool writehist(FILE *hist, historyheadtype *histhead); void save_history(void); #endif @@ -461,6 +467,12 @@ const char *revstristr(const char *haystack, const char *needle, const #ifndef HAVE_STRNLEN size_t nstrnlen(const char *s, size_t maxlen); #endif +#ifndef HAVE_GETLINE +ssize_t ngetline(char **lineptr, size_t *n, FILE *stream); +#endif +#ifndef HAVE_GETDELIM +ssize_t ngetdelim(char **lineptr, size_t *n, int delim, FILE *stream); +#endif const char *strstrwrapper(const char *haystack, const char *needle, const char *start); void nperror(const char *s); diff --git a/src/rcfile.c b/src/rcfile.c index 579bf6c4..20b9de25 100644 --- a/src/rcfile.c +++ b/src/rcfile.c @@ -101,7 +101,7 @@ const static rcoption rcopts[] = { static bool errors = FALSE; static int lineno = 0; -static char *nanorc; +static const char *nanorc; /* We have an error in some part of the rcfile; put it on stderr and make the user hit return to continue starting up nano. */ @@ -648,16 +648,13 @@ void parse_rcfile(FILE *rcstream) void do_rcfile(void) { FILE *rcstream; - const struct passwd *userage; - uid_t euid = geteuid(); - char *homenv = getenv("HOME"); #ifdef SYSCONFDIR assert(sizeof(SYSCONFDIR) == strlen(SYSCONFDIR) + 1); - nanorc = charalloc(sizeof(SYSCONFDIR) + 7); - sprintf(nanorc, "%s/nanorc", SYSCONFDIR); + nanorc = SYSCONFDIR "/nanorc"; /* Try to open system nanorc */ - if ((rcstream = fopen(nanorc, "r")) != NULL) { + rcstream = fopen(nanorc, "r"); + if (rcstream != NULL) { /* Parse it! */ parse_rcfile(rcstream); fclose(rcstream); @@ -666,33 +663,38 @@ void do_rcfile(void) lineno = 0; - /* Rely on $HOME, fall back on getpwuid() */ - if (homenv != NULL) { - nanorc = charealloc(nanorc, strlen(homenv) + 10); - sprintf(nanorc, "%s/.nanorc", homenv); - } else { - userage = getpwuid(euid); - endpwent(); + { + const char *homenv = getenv("HOME"); - if (userage == NULL) { - rcfile_error(N_("I can't find my home directory! Wah!")); - SET(NO_RCFILE); - } else { - nanorc = charealloc(nanorc, strlen(userage->pw_dir) + 9); - sprintf(nanorc, "%s/.nanorc", userage->pw_dir); + /* Rely on $HOME, fall back on getpwuid() */ + if (homenv == NULL) { + const struct passwd *userage = getpwuid(geteuid()); + if (userage != NULL) + homenv = userage->pw_dir; } + homedir = mallocstrcpy(NULL, homenv); } - if (!ISSET(NO_RCFILE)) { + if (homedir == NULL) { + rcfile_error(N_("I can't find my home directory! Wah!")); + SET(NO_RCFILE); + } else { + size_t homelen = strlen(homedir); + char *nanorcf = charalloc(homelen + 9); + + nanorc = nanorcf; + strcpy(nanorcf, homedir); + strcpy(nanorcf + homelen, "/.nanorc"); #if defined(DISABLE_ROOTWRAP) && !defined(DISABLE_WRAPPING) /* If we've already read SYSCONFDIR/nanorc (if it's there), we're root, and --disable-wrapping-as-root is used, turn wrapping off */ - if (euid == NANO_ROOT_UID) + if (geteuid() == NANO_ROOT_UID) SET(NO_WRAP); #endif - if ((rcstream = fopen(nanorc, "r")) == NULL) { + rcstream = fopen(nanorc, "r"); + if (rcstream == NULL) { /* Don't complain about the file not existing */ if (errno != ENOENT) { rcfile_error(N_("Error reading %s: %s"), nanorc, strerror(errno)); @@ -702,10 +704,10 @@ void do_rcfile(void) parse_rcfile(rcstream); fclose(rcstream); } + free(nanorcf); } lineno = 0; - free(nanorc); #ifdef ENABLE_COLOR set_colorpairs(); #endif diff --git a/src/utils.c b/src/utils.c index 4e6b5880..5619f936 100644 --- a/src/utils.c +++ b/src/utils.c @@ -240,6 +240,65 @@ size_t nstrnlen(const char *s, size_t maxlen) } #endif +#ifndef HAVE_GETLINE +/* This function is equivalent to getline(). It was adapted from + * GNU mailutils' getline() function. */ +ssize_t ngetline(char **lineptr, size_t *n, FILE *stream) +{ + return getdelim(lineptr, n, '\n', stream); +} +#endif + +#ifndef HAVE_GETDELIM +/* This function is equivalent to getdelim(). It was adapted from + * GNU mailutils' getdelim() function. */ +ssize_t ngetdelim(char **lineptr, size_t *n, int delim, FILE *stream) +{ + static const int line_size = 128; + /* Default value for line length. */ + size_t indx = 0; + int c; + + /* Sanity checks. */ + if (lineptr == NULL || n == NULL || stream == NULL) + return -1; + + /* Allocate the line the first time. */ + if (*lineptr == NULL) { + *lineptr = charalloc(line_size); + *n = line_size; + } + + while ((c = getc(stream)) != EOF) { + /* Check if more memory is needed. */ + if (indx >= *n) { + *lineptr = charealloc(*lineptr, *n + line_size); + *n += line_size; + } + + /* Push the result in the line. */ + (*lineptr)[indx++] = (char)c; + + /* Bail out. */ + if (c == delim) + break; + } + + /* Make room for the null character. */ + if (indx >= *n) { + *lineptr = charealloc(*lineptr, *n + line_size); + *n += line_size; + } + + /* Null terminate the buffer. */ + (*lineptr)[indx++] = '\0'; + + /* The last line may not have the delimiter, we have to return what + * we got and the error will be seen on the next iteration. */ + return (c == EOF && (indx - 1) == 0) ? -1 : indx - 1; +} +#endif + /* If we are searching backwards, we will find the last match that * starts no later than start. Otherwise we find the first match * starting no earlier than start. If we are doing a regexp search, we -- 2.39.5