1252190Srpaulo/*
2252190Srpaulo * Command line editing and history wrapper for readline
3252190Srpaulo * Copyright (c) 2010, Jouni Malinen <j@w1.fi>
4252190Srpaulo *
5252190Srpaulo * This software may be distributed under the terms of the BSD license.
6252190Srpaulo * See README for more details.
7252190Srpaulo */
8252190Srpaulo
9252190Srpaulo#include "includes.h"
10252190Srpaulo#include <readline/readline.h>
11252190Srpaulo#include <readline/history.h>
12252190Srpaulo
13252190Srpaulo#include "common.h"
14252190Srpaulo#include "eloop.h"
15252190Srpaulo#include "edit.h"
16252190Srpaulo
17252190Srpaulo
18252190Srpaulostatic void *edit_cb_ctx;
19252190Srpaulostatic void (*edit_cmd_cb)(void *ctx, char *cmd);
20252190Srpaulostatic void (*edit_eof_cb)(void *ctx);
21252190Srpaulostatic char ** (*edit_completion_cb)(void *ctx, const char *cmd, int pos) =
22252190Srpaulo	NULL;
23252190Srpaulo
24252190Srpaulostatic char **pending_completions = NULL;
25252190Srpaulo
26252190Srpaulo
27252190Srpaulostatic void readline_free_completions(void)
28252190Srpaulo{
29252190Srpaulo	int i;
30252190Srpaulo	if (pending_completions == NULL)
31252190Srpaulo		return;
32252190Srpaulo	for (i = 0; pending_completions[i]; i++)
33252190Srpaulo		os_free(pending_completions[i]);
34252190Srpaulo	os_free(pending_completions);
35252190Srpaulo	pending_completions = NULL;
36252190Srpaulo}
37252190Srpaulo
38252190Srpaulo
39252190Srpaulostatic char * readline_completion_func(const char *text, int state)
40252190Srpaulo{
41252190Srpaulo	static int pos = 0;
42252190Srpaulo	static size_t len = 0;
43252190Srpaulo
44252190Srpaulo	if (pending_completions == NULL) {
45252190Srpaulo		rl_attempted_completion_over = 1;
46252190Srpaulo		return NULL;
47252190Srpaulo	}
48252190Srpaulo
49252190Srpaulo	if (state == 0) {
50252190Srpaulo		pos = 0;
51252190Srpaulo		len = os_strlen(text);
52252190Srpaulo	}
53252190Srpaulo	for (; pending_completions[pos]; pos++) {
54252190Srpaulo		if (strncmp(pending_completions[pos], text, len) == 0)
55252190Srpaulo			return strdup(pending_completions[pos++]);
56252190Srpaulo	}
57252190Srpaulo
58252190Srpaulo	rl_attempted_completion_over = 1;
59252190Srpaulo	return NULL;
60252190Srpaulo}
61252190Srpaulo
62252190Srpaulo
63252190Srpaulostatic char ** readline_completion(const char *text, int start, int end)
64252190Srpaulo{
65252190Srpaulo	readline_free_completions();
66252190Srpaulo	if (edit_completion_cb)
67252190Srpaulo		pending_completions = edit_completion_cb(edit_cb_ctx,
68252190Srpaulo							 rl_line_buffer, end);
69252190Srpaulo	return rl_completion_matches(text, readline_completion_func);
70252190Srpaulo}
71252190Srpaulo
72252190Srpaulo
73252190Srpaulostatic void edit_read_char(int sock, void *eloop_ctx, void *sock_ctx)
74252190Srpaulo{
75252190Srpaulo	rl_callback_read_char();
76252190Srpaulo}
77252190Srpaulo
78252190Srpaulo
79252190Srpaulostatic void trunc_nl(char *str)
80252190Srpaulo{
81252190Srpaulo	char *pos = str;
82252190Srpaulo	while (*pos != '\0') {
83252190Srpaulo		if (*pos == '\n') {
84252190Srpaulo			*pos = '\0';
85252190Srpaulo			break;
86252190Srpaulo		}
87252190Srpaulo		pos++;
88252190Srpaulo	}
89252190Srpaulo}
90252190Srpaulo
91252190Srpaulo
92252190Srpaulostatic void readline_cmd_handler(char *cmd)
93252190Srpaulo{
94252190Srpaulo	if (cmd && *cmd) {
95252190Srpaulo		HIST_ENTRY *h;
96252190Srpaulo		while (next_history())
97252190Srpaulo			;
98252190Srpaulo		h = previous_history();
99252190Srpaulo		if (h == NULL || os_strcmp(cmd, h->line) != 0)
100252190Srpaulo			add_history(cmd);
101252190Srpaulo		next_history();
102252190Srpaulo	}
103252190Srpaulo	if (cmd == NULL) {
104252190Srpaulo		edit_eof_cb(edit_cb_ctx);
105252190Srpaulo		return;
106252190Srpaulo	}
107252190Srpaulo	trunc_nl(cmd);
108252190Srpaulo	edit_cmd_cb(edit_cb_ctx, cmd);
109252190Srpaulo}
110252190Srpaulo
111252190Srpaulo
112252190Srpauloint edit_init(void (*cmd_cb)(void *ctx, char *cmd),
113252190Srpaulo	      void (*eof_cb)(void *ctx),
114252190Srpaulo	      char ** (*completion_cb)(void *ctx, const char *cmd, int pos),
115252190Srpaulo	      void *ctx, const char *history_file, const char *ps)
116252190Srpaulo{
117252190Srpaulo	edit_cb_ctx = ctx;
118252190Srpaulo	edit_cmd_cb = cmd_cb;
119252190Srpaulo	edit_eof_cb = eof_cb;
120252190Srpaulo	edit_completion_cb = completion_cb;
121252190Srpaulo
122252190Srpaulo	rl_attempted_completion_function = readline_completion;
123252190Srpaulo	if (history_file) {
124252190Srpaulo		read_history(history_file);
125252190Srpaulo		stifle_history(100);
126252190Srpaulo	}
127252190Srpaulo
128252190Srpaulo	eloop_register_read_sock(STDIN_FILENO, edit_read_char, NULL, NULL);
129252190Srpaulo
130252190Srpaulo	if (ps) {
131252190Srpaulo		size_t blen = os_strlen(ps) + 3;
132252190Srpaulo		char *ps2 = os_malloc(blen);
133252190Srpaulo		if (ps2) {
134252190Srpaulo			os_snprintf(ps2, blen, "%s> ", ps);
135252190Srpaulo			rl_callback_handler_install(ps2, readline_cmd_handler);
136252190Srpaulo			os_free(ps2);
137252190Srpaulo			return 0;
138252190Srpaulo		}
139252190Srpaulo	}
140252190Srpaulo
141252190Srpaulo	rl_callback_handler_install("> ", readline_cmd_handler);
142252190Srpaulo
143252190Srpaulo	return 0;
144252190Srpaulo}
145252190Srpaulo
146252190Srpaulo
147252190Srpaulovoid edit_deinit(const char *history_file,
148252190Srpaulo		 int (*filter_cb)(void *ctx, const char *cmd))
149252190Srpaulo{
150252190Srpaulo	rl_set_prompt("");
151252190Srpaulo	rl_replace_line("", 0);
152252190Srpaulo	rl_redisplay();
153252190Srpaulo	rl_callback_handler_remove();
154252190Srpaulo	readline_free_completions();
155252190Srpaulo
156252190Srpaulo	eloop_unregister_read_sock(STDIN_FILENO);
157252190Srpaulo
158252190Srpaulo	if (history_file) {
159252190Srpaulo		/* Save command history, excluding lines that may contain
160252190Srpaulo		 * passwords. */
161252190Srpaulo		HIST_ENTRY *h;
162252190Srpaulo		history_set_pos(0);
163252190Srpaulo		while ((h = current_history())) {
164252190Srpaulo			char *p = h->line;
165252190Srpaulo			while (*p == ' ' || *p == '\t')
166252190Srpaulo				p++;
167252190Srpaulo			if (filter_cb && filter_cb(edit_cb_ctx, p)) {
168252190Srpaulo				h = remove_history(where_history());
169252190Srpaulo				if (h) {
170252190Srpaulo					os_free(h->line);
171252190Srpaulo					free(h->data);
172252190Srpaulo					os_free(h);
173252190Srpaulo				} else
174252190Srpaulo					next_history();
175252190Srpaulo			} else
176252190Srpaulo				next_history();
177252190Srpaulo		}
178252190Srpaulo		write_history(history_file);
179252190Srpaulo	}
180252190Srpaulo}
181252190Srpaulo
182252190Srpaulo
183252190Srpaulovoid edit_clear_line(void)
184252190Srpaulo{
185252190Srpaulo}
186252190Srpaulo
187252190Srpaulo
188252190Srpaulovoid edit_redraw(void)
189252190Srpaulo{
190252190Srpaulo	rl_on_new_line();
191252190Srpaulo	rl_redisplay();
192252190Srpaulo}
193