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
18static void *edit_cb_ctx;
19static void (*edit_cmd_cb)(void *ctx, char *cmd);
20static void (*edit_eof_cb)(void *ctx);
21static char ** (*edit_completion_cb)(void *ctx, const char *cmd, int pos) =
22	NULL;
23
24static char **pending_completions = NULL;
25
26
27static 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
39static 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
63static 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
73static void edit_read_char(int sock, void *eloop_ctx, void *sock_ctx)
74{
75	rl_callback_read_char();
76}
77
78
79static 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
92static 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
112int 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
147void 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
183void edit_clear_line(void)
184{
185}
186
187
188void edit_redraw(void)
189{
190	rl_on_new_line();
191	rl_redisplay();
192}
193