1 /*
2  * Command line editing and history wrapper for readline
3  * Copyright (c) 2010, Jouni Malinen <j@w1.fi>
4  *
5  * This software may be distributed under the terms of the BSD license.
6  * See README for more details.
7  */
8 
9 #include "includes.h"
10 #include <readline/readline.h>
11 #include <readline/history.h>
12 
13 #include "common.h"
14 #include "eloop.h"
15 #include "edit.h"
16 
17 
18 static void *edit_cb_ctx;
19 static void (*edit_cmd_cb)(void *ctx, char *cmd);
20 static void (*edit_eof_cb)(void *ctx);
21 static char ** (*edit_completion_cb)(void *ctx, const char *cmd, int pos) =
22 	NULL;
23 
24 static char **pending_completions = NULL;
25 
26 
27 static void readline_free_completions(void)
28 {
29 	int i;
30 	if (pending_completions == NULL)
31 		return;
32 	for (i = 0; pending_completions[i]; i++)
33 		os_free(pending_completions[i]);
34 	os_free(pending_completions);
35 	pending_completions = NULL;
36 }
37 
38 
39 static char * readline_completion_func(const char *text, int state)
40 {
41 	static int pos = 0;
42 	static size_t len = 0;
43 
44 	if (pending_completions == NULL) {
45 		rl_attempted_completion_over = 1;
46 		return NULL;
47 	}
48 
49 	if (state == 0) {
50 		pos = 0;
51 		len = os_strlen(text);
52 	}
53 	for (; pending_completions[pos]; pos++) {
54 		if (strncmp(pending_completions[pos], text, len) == 0)
55 			return strdup(pending_completions[pos++]);
56 	}
57 
58 	rl_attempted_completion_over = 1;
59 	return NULL;
60 }
61 
62 
63 static char ** readline_completion(const char *text, int start, int end)
64 {
65 	readline_free_completions();
66 	if (edit_completion_cb)
67 		pending_completions = edit_completion_cb(edit_cb_ctx,
68 							 rl_line_buffer, end);
69 	return rl_completion_matches(text, readline_completion_func);
70 }
71 
72 
73 static void edit_read_char(int sock, void *eloop_ctx, void *sock_ctx)
74 {
75 	rl_callback_read_char();
76 }
77 
78 
79 static void trunc_nl(char *str)
80 {
81 	char *pos = str;
82 	while (*pos != '\0') {
83 		if (*pos == '\n') {
84 			*pos = '\0';
85 			break;
86 		}
87 		pos++;
88 	}
89 }
90 
91 
92 static void readline_cmd_handler(char *cmd)
93 {
94 	if (cmd && *cmd) {
95 		HIST_ENTRY *h;
96 		while (next_history())
97 			;
98 		h = previous_history();
99 		if (h == NULL || os_strcmp(cmd, h->line) != 0)
100 			add_history(cmd);
101 		next_history();
102 	}
103 	if (cmd == NULL) {
104 		edit_eof_cb(edit_cb_ctx);
105 		return;
106 	}
107 	trunc_nl(cmd);
108 	edit_cmd_cb(edit_cb_ctx, cmd);
109 }
110 
111 
112 int edit_init(void (*cmd_cb)(void *ctx, char *cmd),
113 	      void (*eof_cb)(void *ctx),
114 	      char ** (*completion_cb)(void *ctx, const char *cmd, int pos),
115 	      void *ctx, const char *history_file, const char *ps)
116 {
117 	edit_cb_ctx = ctx;
118 	edit_cmd_cb = cmd_cb;
119 	edit_eof_cb = eof_cb;
120 	edit_completion_cb = completion_cb;
121 
122 	rl_attempted_completion_function = readline_completion;
123 	if (history_file) {
124 		read_history(history_file);
125 		stifle_history(100);
126 	}
127 
128 	eloop_register_read_sock(STDIN_FILENO, edit_read_char, NULL, NULL);
129 
130 	if (ps) {
131 		size_t blen = os_strlen(ps) + 3;
132 		char *ps2 = os_malloc(blen);
133 		if (ps2) {
134 			os_snprintf(ps2, blen, "%s> ", ps);
135 			rl_callback_handler_install(ps2, readline_cmd_handler);
136 			os_free(ps2);
137 			return 0;
138 		}
139 	}
140 
141 	rl_callback_handler_install("> ", readline_cmd_handler);
142 
143 	return 0;
144 }
145 
146 
147 void edit_deinit(const char *history_file,
148 		 int (*filter_cb)(void *ctx, const char *cmd))
149 {
150 	rl_set_prompt("");
151 	rl_replace_line("", 0);
152 	rl_redisplay();
153 	rl_callback_handler_remove();
154 	readline_free_completions();
155 
156 	eloop_unregister_read_sock(STDIN_FILENO);
157 
158 	if (history_file) {
159 		/* Save command history, excluding lines that may contain
160 		 * passwords. */
161 		HIST_ENTRY *h;
162 		history_set_pos(0);
163 		while ((h = current_history())) {
164 			char *p = h->line;
165 			while (*p == ' ' || *p == '\t')
166 				p++;
167 			if (filter_cb && filter_cb(edit_cb_ctx, p)) {
168 				h = remove_history(where_history());
169 				if (h) {
170 					free(h->line);
171 					free(h->data);
172 					free(h);
173 				} else
174 					next_history();
175 			} else
176 				next_history();
177 		}
178 		write_history(history_file);
179 	}
180 }
181 
182 
183 void edit_clear_line(void)
184 {
185 }
186 
187 
188 void edit_redraw(void)
189 {
190 	rl_on_new_line();
191 	rl_redisplay();
192 }
193