[PATCH 3/3] chsh: make readline completion to propose valid shells

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This is better than default readline completion, that gives paths from
current directory onwards.

Signed-off-by: Sami Kerola <kerolasa@xxxxxx>
---
 login-utils/chsh.c | 117 ++++++++++++++++++++++++++++++++++++++++-------------
 1 file changed, 90 insertions(+), 27 deletions(-)

diff --git a/login-utils/chsh.c b/login-utils/chsh.c
index 1fb377593..c6eab1554 100644
--- a/login-utils/chsh.c
+++ b/login-utils/chsh.c
@@ -67,6 +67,8 @@ struct sinfo {
 	char *shell;
 };
 
+/* global due readline completion */
+static char **global_shells = NULL;
 
 static void __attribute__((__noreturn__)) usage (FILE *fp)
 {
@@ -87,24 +89,31 @@ static void __attribute__((__noreturn__)) usage (FILE *fp)
 }
 
 /*
- *  get_shell_list () -- if the given shell appears in /etc/shells,
- *	return true.  if not, return false.
- *	if the given shell is NULL, /etc/shells is outputted to stdout.
+ *  free_shells () -- free shells allocations.
  */
-static int get_shell_list(const char *shell_name)
+static void free_shells(void)
+{
+	char **s;
+
+	for (s = global_shells; *s; s++)
+		free(*s);
+	free(global_shells);
+}
+
+/*
+ *  init_shells () -- fill shells variable from /etc/shells,
+ */
+static int init_shells(char ***shells)
 {
 	FILE *fp;
-	int found = 0;
 	char *buf = NULL;
-	size_t sz = 0;
+	size_t sz = 0, shellsz = 8, n = 0;
 	ssize_t len;
 
+	*shells = xmalloc(sizeof(char *) * shellsz);
 	fp = fopen(_PATH_SHELLS, "r");
-	if (!fp) {
-		if (!shell_name)
-			warnx(_("No known shells."));
-		return 0;
-	}
+	if (!fp)
+		return 1;
 	while ((len = getline(&buf, &sz, fp)) != -1) {
 		/* ignore comments and blank lines */
 		if (*buf == '#' || len < 2)
@@ -112,26 +121,77 @@ static int get_shell_list(const char *shell_name)
 		/* strip the ending newline */
 		if (buf[len - 1] == '\n')
 			buf[len - 1] = 0;
-		/* check or output the shell */
+		(*shells)[n++] = buf;
+		if (shellsz < n) {
+			shellsz *= 2;
+			shells = xrealloc(shells, sizeof(char *) * shellsz);
+		}
+		buf = NULL;
+	}
+	free(buf);
+	(*shells)[n] = NULL;
+	fclose(fp);
+	atexit(free_shells);
+	return 0;
+}
+
+/*
+ *  get_shell_list () -- if the given shell appears in /etc/shells,
+ *	return true.  if not, return false.
+ *	if the given shell is NULL, /etc/shells is outputted to stdout.
+ */
+static int get_shell_list(const char *shell_name, char ***shells)
+{
+	char **s;
+	int found = 0;
+
+	if (!shells)
+		return found;
+	s = *shells;
+	for (s = *shells; *s; s++) {
 		if (shell_name) {
-			if (!strcmp(shell_name, buf)) {
+			if (!strcmp(shell_name, *s)) {
 				found = 1;
 				break;
 			}
 		} else
-			printf("%s\n", buf);
+			printf("%s\n", *s);
 	}
-	fclose(fp);
-	free(buf);
 	return found;
 }
 
+#ifdef HAVE_LIBREADLINE
+static char *shell_name_generator(const char *text, int state)
+{
+	static size_t len, idx;
+	char *s;
+
+	if (!state) {
+		idx = 0;
+		len = strlen(text);
+	}
+	while ((s = global_shells[idx++])) {
+		if (strncmp(s, text, len) == 0)
+			return xstrdup(s);
+	}
+	return NULL;
+}
+
+static char **shell_name_completion(const char *text,
+				    int start __attribute__((__unused__)),
+				    int end __attribute__((__unused__)))
+{
+	rl_attempted_completion_over = 1;
+	return rl_completion_matches(text, shell_name_generator);
+}
+#endif
+
 /*
  *  parse_argv () --
  *	parse the command line arguments, and fill in "pinfo" with any
  *	information from the command line.
  */
-static void parse_argv(int argc, char **argv, struct sinfo *pinfo)
+static void parse_argv(int argc, char **argv, struct sinfo *pinfo, char ***shells)
 {
 	static const struct option long_options[] = {
 		{"shell",       required_argument, NULL, 's'},
@@ -151,7 +211,8 @@ static void parse_argv(int argc, char **argv, struct sinfo *pinfo)
 		case 'h':
 			usage(stdout);
 		case 'l':
-			get_shell_list(NULL);
+			init_shells(shells);
+			get_shell_list(NULL, shells);
 			exit(EXIT_SUCCESS);
 		case 's':
 			if (!optarg)
@@ -178,15 +239,16 @@ static char *ask_new_shell(char *question, char *oldshell)
 {
 	int len;
 	char *ans = NULL;
-#ifndef HAVE_LIBREADLINE
+#ifdef HAVE_LIBREADLINE
+	rl_attempted_completion_function = shell_name_completion;
+#else
 	size_t dummy = 0;
 #endif
-
 	if (!oldshell)
 		oldshell = "";
-	printf("%s [%s]: ", question, oldshell);
+	printf("%s [%s]\n", question, oldshell);
 #ifdef HAVE_LIBREADLINE
-	if ((ans = readline(NULL)) == NULL)
+	if ((ans = readline("> ")) == NULL)
 #else
 	if (getline(&ans, &dummy, stdin) < 0)
 #endif
@@ -203,7 +265,7 @@ static char *ask_new_shell(char *question, char *oldshell)
  *  check_shell () -- if the shell is completely invalid, print
  *	an error and exit.
  */
-static void check_shell(const char *shell)
+static void check_shell(const char *shell, char ***shells)
 {
 	if (*shell != '/')
 		errx(EXIT_FAILURE, _("shell must be a full path name"));
@@ -213,7 +275,7 @@ static void check_shell(const char *shell)
 		errx(EXIT_FAILURE, _("\"%s\" is not executable"), shell);
 	if (illegal_passwd_chars(shell))
 		errx(EXIT_FAILURE, _("%s: has illegal characters"), shell);
-	if (!get_shell_list(shell)) {
+	if (!get_shell_list(shell, shells)) {
 #ifdef ONLY_LISTED_SHELLS
 		if (!getuid())
 			warnx(_("Warning: \"%s\" is not listed in %s."), shell,
@@ -245,7 +307,7 @@ int main(int argc, char **argv)
 	textdomain(PACKAGE);
 	atexit(close_stdout);
 
-	parse_argv(argc, argv, &info);
+	parse_argv(argc, argv, &info, &global_shells);
 	if (!info.username) {
 		pw = getpwuid(uid);
 		if (!pw)
@@ -304,7 +366,8 @@ int main(int argc, char **argv)
 		    _("running UID doesn't match UID of user we're "
 		      "altering, shell change denied"));
 	}
-	if (uid != 0 && !get_shell_list(oldshell)) {
+	init_shells(&global_shells);
+	if (uid != 0 && !get_shell_list(oldshell, &global_shells)) {
 		errno = EACCES;
 		err(EXIT_FAILURE, _("your shell is not in %s, "
 				    "shell change denied"), _PATH_SHELLS);
@@ -323,7 +386,7 @@ int main(int argc, char **argv)
 			return EXIT_SUCCESS;
 	}
 
-	check_shell(info.shell);
+	check_shell(info.shell, &global_shells);
 
 	if (!nullshell && strcmp(oldshell, info.shell) == 0)
 		errx(EXIT_SUCCESS, _("Shell not changed."));
-- 
2.13.0

--
To unsubscribe from this list: send the line "unsubscribe util-linux" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Netdev]     [Ethernet Bridging]     [Linux Wireless]     [Kernel Newbies]     [Security]     [Linux for Hams]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux Admin]     [Samba]

  Powered by Linux