diff --git a/NEWS b/NEWS
index 1313ab2..4ba05e2 100644
--- a/NEWS
+++ b/NEWS
@@ -22,6 +22,10 @@ Version 2.23
   19049, 19050, 19059, 19071, 19074, 19076, 19077, 19078, 19079, 19085,
   19086, 19088, 19094, 19095, 19124, 19125, 19129, 19134, 19137.
 
+* The getusershell function now returns entries from /etc/shells only when
+  the line starts with a '/', and no longer chops lines based on '#' or white
+  space.
+
 * There is now a --disable-timezone-tools configure option for disabling the
   building and installing of the timezone related utilities (zic, zdump, and
   tzselect).  This is useful for people who build the timezone data and code
diff --git a/misc/Makefile b/misc/Makefile
index 2f5edf6..3fc44f1 100644
--- a/misc/Makefile
+++ b/misc/Makefile
@@ -77,7 +77,8 @@ gpl2lgpl := error.c error.h
 
 tests := tst-dirname tst-tsearch tst-fdset tst-efgcvt tst-mntent tst-hsearch \
 	 tst-error1 tst-pselect tst-insremque tst-mntent2 bug-hsearch1 \
-	 tst-mntent-blank-corrupt tst-mntent-blank-passno
+	 tst-mntent-blank-corrupt tst-mntent-blank-passno tst-getusershell \
+	 tst-endusershell
 ifeq ($(run-built-tests),yes)
 tests-special += $(objpfx)tst-error1-mem.out
 endif
diff --git a/misc/getusershell.c b/misc/getusershell.c
index fc2c43b..3d3f221 100644
--- a/misc/getusershell.c
+++ b/misc/getusershell.c
@@ -1,143 +1,112 @@
-/*
- * Copyright (c) 1985, 1993
- *	The Regents of the University of California.  All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- * 4. Neither the name of the University nor the names of its contributors
- *    may be used to endorse or promote products derived from this software
- *    without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-#if defined(LIBC_SCCS) && !defined(lint)
-static char sccsid[] = "@(#)getusershell.c	8.1 (Berkeley) 6/4/93";
-#endif /* LIBC_SCCS and not lint */
-
-#include <sys/param.h>
-#include <sys/file.h>
-#include <sys/stat.h>
+/* Copyright (C) 2015 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <assert.h>
+#include <paths.h>
 #include <stdio.h>
-#include <stdio_ext.h>
-#include <ctype.h>
 #include <stdlib.h>
+#include <string.h>
 #include <unistd.h>
-#include <paths.h>
 
-/*
- * Local shells should NOT be added here.  They should be added in
- * /etc/shells.
- */
-
-/* NB: we do not initialize okshells here.  The initialization needs
-   relocations.  These interfaces are used so rarely that this is not
-   justified.  Instead explicitly initialize the array when it is
-   used.  */
-#if 0
-static const char *const okshells[] = { _PATH_BSHELL, _PATH_CSHELL, NULL };
-#else
-static const char *okshells[3];
+/* Used by tests to point to custom paths.  */
+#ifndef _LOCAL_PATH_SHELLS
+# define _LOCAL_PATH_SHELLS _PATH_SHELLS
 #endif
-static char **curshell, **shells, *strings;
-static char **initshells (void) __THROW;
 
-/*
- * Get a list of shells from _PATH_SHELLS, if it exists.
- */
+/* These functions are not thread safe (by design), so globals are OK.  */
+static FILE *shellfp;
+static int okshell_idx;
+static char *shellbuf;
+static size_t buf_len;
+
 char *
 getusershell (void)
 {
-	char *ret;
-
-	if (curshell == NULL)
-		curshell = initshells();
-	ret = *curshell;
-	if (ret != NULL)
-		curshell++;
-	return (ret);
+  /* Handle the builtin case first.
+
+     NB: we do not use a global array here as the initialization needs
+     relocations.  These interfaces are used so rarely that this is not
+     justified.  Instead open code it with a switch statement.  */
+  switch (okshell_idx)
+    {
+    case 0:
+      if (shellfp == NULL)
+	{
+	  setusershell ();
+	  return getusershell ();
+	}
+      break;
+    case 1:
+      okshell_idx++;
+      return (char *) _PATH_BSHELL;
+    case 2:
+      okshell_idx++;
+      return (char *) _PATH_CSHELL;
+    case 3:
+      return NULL;
+    }
+
+  /* Walk the /etc/shells file.  */
+  while (1)
+    {
+      ssize_t len;
+      char *p;
+
+      len = getline (&shellbuf, &buf_len, shellfp);
+      if (len <= 0)
+	break;
+
+      /* Chop the trailing newline.  */
+      p = shellbuf + len - 1;
+      if (*p == '\n')
+	*p = '\0';
+
+      /* Only accept valid shells (which we define as starting with a '/').  */
+      if (shellbuf[0] == '/')
+	return shellbuf;
+    }
+
+  /* No more valid shells.  */
+  return NULL;
 }
 
+hidden_proto (endusershell);
 void
 endusershell (void)
 {
-
-	free(shells);
-	shells = NULL;
-	free(strings);
-	strings = NULL;
-	curshell = NULL;
+  okshell_idx = 0;
+  if (shellfp)
+    {
+      fclose (shellfp);
+      shellfp = NULL;
+      free (shellbuf);
+      shellbuf = NULL;
+    }
 }
+hidden_def (endusershell);
 
 void
 setusershell (void)
 {
+  /* We could rewind shellfp here, but we get smaller code this way.
+     Keep in mind this is not a performance sensitive API.  */
+  endusershell ();
 
-	curshell = initshells();
-}
-
-static char **
-initshells (void)
-{
-	char **sp, *cp;
-	FILE *fp;
-	struct stat64 statb;
-	size_t flen;
-
-	free(shells);
-	shells = NULL;
-	free(strings);
-	strings = NULL;
-	if ((fp = fopen(_PATH_SHELLS, "rce")) == NULL)
-		goto init_okshells_noclose;
-	if (fstat64(fileno(fp), &statb) == -1) {
-	init_okshells:
-		(void)fclose(fp);
-	init_okshells_noclose:
-		okshells[0] = _PATH_BSHELL;
-		okshells[1] = _PATH_CSHELL;
-		return (char **) okshells;
-	}
-	if (statb.st_size > ~(size_t)0 / sizeof (char *) * 3)
-		goto init_okshells;
-	flen = statb.st_size + 3;
-	if ((strings = malloc(flen)) == NULL)
-		goto init_okshells;
-	shells = malloc(statb.st_size / 3 * sizeof (char *));
-	if (shells == NULL) {
-		free(strings);
-		strings = NULL;
-		goto init_okshells;
-	}
-	sp = shells;
-	cp = strings;
-	while (fgets_unlocked(cp, flen - (cp - strings), fp) != NULL) {
-		while (*cp != '#' && *cp != '/' && *cp != '\0')
-			cp++;
-		if (*cp == '#' || *cp == '\0' || cp[1] == '\0')
-			continue;
-		*sp++ = cp;
-		while (!isspace(*cp) && *cp != '#' && *cp != '\0')
-			cp++;
-		*cp++ = '\0';
-	}
-	*sp = NULL;
-	(void)fclose(fp);
-	return (shells);
+  shellfp = fopen (_LOCAL_PATH_SHELLS, "rce");
+  if (shellfp == NULL)
+    okshell_idx = 1;
 }
diff --git a/misc/tst-endusershell.c b/misc/tst-endusershell.c
new file mode 100644
index 0000000..95c2143
--- /dev/null
+++ b/misc/tst-endusershell.c
@@ -0,0 +1,64 @@
+/* Make sure calling endusershell first doesn't crash.
+
+   Copyright (C) 2015 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+static int do_test (void);
+
+#define TEST_FUNCTION do_test ()
+#include "../test-skeleton.c"
+
+static int
+do_test (void)
+{
+  /* This should not crash or corrupt.  */
+  endusershell ();
+  endusershell ();
+  endusershell ();
+
+  /* Neither should this.  */
+  setusershell ();
+  setusershell ();
+  setusershell ();
+  setusershell ();
+  setusershell ();
+
+  /* Shouldn't be a problem, but we can't really verify the return since
+     it depends on /etc/shells in the installed system.  */
+  getusershell ();
+  getusershell ();
+  getusershell ();
+  getusershell ();
+  getusershell ();
+  getusershell ();
+
+  /* Test resetting behavior.  */
+  setusershell ();
+  getusershell ();
+  setusershell ();
+  getusershell ();
+
+  /* Test circular behavior.  */
+  setusershell ();
+  getusershell ();
+  endusershell ();
+  setusershell ();
+  getusershell ();
+  endusershell ();
+
+  return 0;
+}
diff --git a/misc/tst-getusershell.c b/misc/tst-getusershell.c
new file mode 100644
index 0000000..27fc4f8
--- /dev/null
+++ b/misc/tst-getusershell.c
@@ -0,0 +1,178 @@
+/* Verify behavior of getusershell w/various inputs.
+
+   Copyright (C) 2015 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+/* Set the functions to read from our custom file so we can control the
+   exact content for the tests.  */
+static char *path_shells;
+#define _LOCAL_PATH_SHELLS path_shells
+#include "getusershell.c"
+
+#include <assert.h>
+
+static int do_test (void);
+
+#define TEST_FUNCTION do_test ()
+#include "../test-skeleton.c"
+
+static int path_shells_fd;
+
+static void
+set_content (const char *content)
+{
+  size_t len = strlen (content);
+
+  assert (lseek (path_shells_fd, 0, SEEK_SET) == 0);
+  assert (ftruncate (path_shells_fd, 0) == 0);
+  assert (write (path_shells_fd, content, len) == len);
+
+  /* Reset the shell reading state since we rewrote the file.  */
+  endusershell ();
+}
+
+#define dprintf(fmt, args...) printf ("%s: " fmt, __func__, ## args)
+
+/* Verify behavior of getusershell w/out any /etc/shells file.  */
+static int
+check_default (void)
+{
+  char bad_shells[] = "";
+  char *old_shells = path_shells;
+  const char *shell;
+  int ret = 1, i;
+
+  /* This will cause fopen to fail.  */
+  path_shells = bad_shells;
+
+  shell = getusershell ();
+  if (shell == NULL || strcmp (shell, "/bin/sh") != 0)
+    {
+      dprintf ("first shell must be /bin/sh, not '%s'\n", shell);
+      goto done;
+    }
+
+  shell = getusershell ();
+  if (shell == NULL || strcmp (shell, "/bin/csh") != 0)
+    {
+      dprintf ("second shell must be /bin/csh, not '%s'\n", shell);
+      goto done;
+    }
+
+  /* Call it a few times to make sure it doesn't wrap or anything.  */
+  for (i = 0; i < 10; ++i)
+    {
+      shell = getusershell ();
+      if (shell != NULL)
+	{
+	  dprintf ("third shell must be NULL, not '%s'\n", shell);
+	  goto done;
+	}
+    }
+
+  ret = 0;
+ done:
+  path_shells = old_shells;
+  return ret;
+}
+
+/* An empty file should return NULL forever.  */
+static int
+check_empty (void)
+{
+  int i;
+  char *shell;
+
+  set_content ("");
+
+  for (i = 0; i < 10; ++i)
+    {
+      shell = getusershell ();
+      if (shell != NULL)
+	{
+	  dprintf ("empty file should return NULL, not '%s'\n", shell);
+	  return 1;
+	}
+    }
+
+  return 0;
+}
+
+/* Verify we handle comments and blank lines correctly.  */
+static int
+check_comments_blank_lines (void)
+{
+  int i;
+  char *shell;
+
+  set_content (
+    /* A mix of comments and blanks.  */
+    "# A comment\n"
+    "  # A comment w/leading spaces\n"
+    "\t\t# A comment w/leading tabs\n"
+    /* Now just blank lines.  */
+    "\n"
+    " \n"
+    "\t \t\n"
+  );
+
+  for (i = 0; i < 10; ++i)
+    {
+      shell = getusershell ();
+      if (shell != NULL)
+	{
+	  dprintf ("comment/blank file should return NULL, not '%s'\n", shell);
+	  return 1;
+	}
+    }
+
+  return 0;
+}
+
+/* Verify "valid" shells which is defined by starting with a /.  */
+static int
+check_valid_shells (void)
+{
+  char *shell;
+
+  set_content (
+    "/bin/foo/bar/ll/pants\n"
+  );
+
+  shell = getusershell ();
+  if (shell == NULL || strcmp (shell, "/bin/foo/bar/ll/pants") != 0)
+    {
+      dprintf ("shell should be /bin/foo/bar/ll/pants, not '%s'\n", shell);
+      return 1;
+    }
+
+  return 0;
+}
+
+static int
+do_test (void)
+{
+  path_shells_fd = create_temp_file ("getusershell", &path_shells);
+  if (path_shells_fd < 0)
+    return 1;
+
+  return
+    check_default () +
+    check_empty () +
+    check_comments_blank_lines () +
+    check_valid_shells ();
+}
