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
diff --git a/ChangeLog b/ChangeLog
index e01a4fe..700f510 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -92,6 +92,14 @@
 	  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 @@
 - 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 @@
 	  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 @@
 	- 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 @@
 - 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 196c4fe..3e3d879 100644
--- a/configure.ac
+++ b/configure.ac
@@ -290,7 +290,7 @@
     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 bddfba9..36e642e 100644
--- a/src/files.c
+++ b/src/files.c
@@ -2914,30 +2914,30 @@
 #endif /* !DISABLE_BROWSER */
 
 #if !defined(NANO_SMALL) && defined(ENABLE_NANORC)
+/* 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)
+{
+    char *nanohist = NULL;
+
+    if (homedir != NULL) {
+	size_t homelen = strlen(homedir);
+
+	nanohist = charalloc(homelen + 15);
+	strcpy(nanohist, homedir);
+	strcpy(nanohist + homelen, "/.nano_history");
+    }
+    return nanohist;
+}
+
 void load_history(void)
 {
-    FILE *hist;
-    const struct passwd *userage = NULL;
-    static char *nanohist;
-    char *buf, *ptr;
-    char *homenv = getenv("HOME");
-    historyheadtype *history = &search_history;
-
-
-    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);
-    }
+    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 @@
 		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 (homenv != NULL || userage != NULL) {
-	hist = fopen(nanohist, "wb");
+    if (nanohist != NULL) {
+	FILE *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 79f8176..f2a947e 100644
--- a/src/global.c
+++ b/src/global.c
@@ -178,6 +178,10 @@
 				 * 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 @@
     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 07ca0c1..3826ef2 100644
--- a/src/nano.c
+++ b/src/nano.c
@@ -3000,7 +3000,7 @@
 	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 be0a7af..b145022 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 3ec5e3d..6f03d74 100644
--- a/src/proto.h
+++ b/src/proto.h
@@ -142,6 +142,10 @@
 
 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_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 @@
 #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 579bf6c..20b9de2 100644
--- a/src/rcfile.c
+++ b/src/rcfile.c
@@ -101,7 +101,7 @@
 
 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 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 @@
 
     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 @@
 	    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 4e6b588..5619f93 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -240,6 +240,65 @@
 }
 #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