1 /* -*- show-trailing-whitespace: t; indent-tabs: t -*-
2  * Copyright (c) 2003,2004,2005,2006 David Lichteblau
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  */
18 #include <curses.h>
19 #include <term.h>
20 #include "common.h"
21 #include <readline/readline.h>
22 #include <readline/history.h>
23 
24 int
carray_cmp(GArray * a,GArray * b)25 carray_cmp(GArray *a, GArray *b)
26 {
27 	int d = memcmp(a->data, b->data, MIN(a->len, b->len));
28 	if (d) return d;
29 	if (a->len < b->len)
30 		return -1;
31 	else if (a->len == b->len)
32 		return 0;
33 	else
34 		return 1;
35 }
36 
37 int
carray_ptr_cmp(const void * aa,const void * bb)38 carray_ptr_cmp(const void *aa, const void *bb)
39 {
40 	GArray *a = *((GArray **) aa);
41 	GArray *b = *((GArray **) bb);
42 	return carray_cmp(a ,b);
43 }
44 
45 void
fdcp(int fdsrc,int fddst)46 fdcp(int fdsrc, int fddst)
47 {
48 	int n;
49 	char buf[4096];
50 
51 	do {
52 		if ( (n = read(fdsrc, buf, sizeof(buf))) == -1) syserr();
53 		if (write(fddst, buf, n) != n) syserr();
54 	} while (n);
55 }
56 
57 void
cp(char * src,char * dst,off_t skip,int append)58 cp(char *src, char *dst, off_t skip, int append)
59 {
60 	int fdsrc, fddst;
61 	int flags = append ? O_WRONLY | O_APPEND : O_CREAT | O_EXCL | O_WRONLY;
62 
63 	if ( (fdsrc = open(src, O_RDONLY)) == -1) syserr();
64 	if (lseek(fdsrc, skip, SEEK_SET) == -1) syserr();
65 	if ( (fddst = open(dst, flags, 0600)) == -1) syserr();
66 	fdcp(fdsrc, fddst);
67 	if (close(fdsrc) == -1) syserr();
68 	if (close(fddst) == -1) syserr();
69 }
70 
71 void
fcopy(FILE * src,FILE * dst)72 fcopy(FILE *src, FILE *dst)
73 {
74 	int n;
75 	char buf[4096];
76 
77 	for (;;) {
78 		if ( (n = fread(buf, 1, sizeof(buf), src)) == 0) {
79 			if (feof(src)) break;
80 			syserr();
81 		}
82 		if (fwrite(buf, 1, n, dst) != n) syserr();
83 	}
84 }
85 
86 static void
print_charbag(char * charbag)87 print_charbag(char *charbag)
88 {
89 	int i;
90 	putchar('[');
91 	for (i = 0; charbag[i]; i++) {
92 		char c = charbag[i];
93 		if (c > 32)
94 			putchar(c);
95 	}
96 	putchar(']');
97 }
98 
99 
100 char
choose(char * prompt,char * charbag,char * help)101 choose(char *prompt, char *charbag, char *help)
102 {
103 	struct termios term;
104 	int c;
105 
106 	if (tcgetattr(0, &term) == -1) syserr();
107 	term.c_lflag &= ~ICANON;
108         term.c_cc[VMIN] = 1;
109         term.c_cc[VTIME] = 0;
110 	for (;;) {
111 		if (tcsetattr(0, TCSANOW, &term) == -1) syserr();
112 		fputs(prompt, stdout);
113 		putchar(' ');
114 		print_charbag(charbag);
115 		putchar(' ');
116 		if (strchr(charbag, c = getchar()))
117 			break;
118 		fputs("\nPlease enter one of ", stdout);
119 		print_charbag(charbag);
120 		putchar('\n');
121 		if (help) printf("  %s", help);
122 		putchar('\n');
123 	}
124 	term.c_lflag |= ICANON;
125 	if (tcsetattr(0, TCSANOW, &term) == -1) syserr();
126 	putchar('\n');
127 	return c;
128 }
129 
130 static long
line_number(char * pathname,long pos)131 line_number(char *pathname, long pos)
132 {
133 	FILE *f;
134 	long line = 1;
135 	int c;
136 
137 	if ( !(f = fopen(pathname, "r+"))) syserr();
138 	while (pos > 0) {
139 		switch ( c = getc_unlocked(f)) {
140 		case EOF:
141 			goto done;
142 		case '\n':
143 			if ( (c = getc_unlocked(f)) != EOF) {
144 				ungetc(c, f);
145 				line++;
146 			}
147 			/* fall through */
148 		default:
149 			pos--;
150 		}
151 	}
152 done:
153 	if (fclose(f) == EOF) syserr();
154 	return line;
155 }
156 
157 void
edit(char * pathname,long line)158 edit(char *pathname, long line)
159 {
160 	int childpid;
161 	int status;
162 	char *vi;
163 
164 	vi = getenv("VISUAL");
165 	if (!vi) vi = getenv("EDITOR");
166 	if (!vi) vi = "vi";
167 
168 	switch ( (childpid = fork())) {
169 	case -1:
170 		syserr();
171 	case 0:
172 		if (line > 0) {
173 			char buf[20];
174 			snprintf(buf, 20, "+%ld", line);
175 			execlp(vi, vi, buf, pathname, 0);
176 		} else
177 			execlp(vi, vi, pathname, 0);
178 		syserr();
179 	}
180 
181 	if (waitpid(childpid, &status, 0) == -1) syserr();
182 	if (!WIFEXITED(status) || WEXITSTATUS(status))
183 		yourfault("editor died");
184 }
185 
186 void
edit_pos(char * pathname,long pos)187 edit_pos(char *pathname, long pos)
188 {
189 	edit(pathname, pos > 0 ? line_number(pathname, pos) : -1);
190 }
191 
192 static int
invalidp(char * ti)193 invalidp(char *ti)
194 {
195 	return ti == 0 || ti == (char *) -1;
196 }
197 
198 void
view(char * pathname)199 view(char *pathname)
200 {
201 	int childpid;
202 	int status;
203 	char *pg;
204 	char *clear = tigetstr("clear");
205 
206 	pg = getenv("PAGER");
207 	if (!pg) pg = "less";
208 
209 	if (!invalidp(clear))
210 		putp(clear);
211 
212 	switch ( (childpid = fork())) {
213 	case -1:
214 		syserr();
215 	case 0:
216 		execlp(pg, pg, pathname, 0);
217 		syserr();
218 	}
219 
220 	if (waitpid(childpid, &status, 0) == -1) syserr();
221 	if (!WIFEXITED(status) || WEXITSTATUS(status))
222 		puts("pager died");
223 }
224 
225 int
pipeview(int * fd)226 pipeview(int *fd)
227 {
228 	int childpid;
229 	char *pg;
230 	char *clear = tigetstr("clear");
231 	int fds[2];
232 
233 	pg = getenv("PAGER");
234 	if (!pg) pg = "less";
235 
236 	if (!invalidp(clear))
237 		putp(clear);
238 
239 	if (pipe(fds) == -1) syserr();
240 
241 	switch ( (childpid = fork())) {
242 	case -1:
243 		syserr();
244 	case 0:
245 		close(fds[1]);
246 		dup2(fds[0], 0);
247 		close(fds[0]);
248 		execlp(pg, pg, 0);
249 		syserr();
250 	}
251 
252 	close(fds[0]);
253 	*fd = fds[1];
254 	return childpid;
255 }
256 
257 void
pipeview_wait(int childpid)258 pipeview_wait(int childpid)
259 {
260 	int status;
261 
262 	if (waitpid(childpid, &status, 0) == -1) syserr();
263 	if (!WIFEXITED(status) || WEXITSTATUS(status))
264 		puts("pager died");
265 }
266 
267 char *
home_filename(char * name)268 home_filename(char *name)
269 {
270 	char *home = getenv("HOME");
271 	int n;
272 	char *result;
273 
274 	if (!home) {
275 		fputs("Warning: You don't have a $HOME.\n", stderr);
276 		return 0;
277 	}
278 
279 	n = strlen(home);
280 	result = xalloc(n + 1 + strlen(name) + 1);
281 	strcpy(result, home);
282 	result[n] = '/';
283 	strcpy(result + n + 1, name);
284 	return result;
285 }
286 
287 
288 static char *
history_filename()289 history_filename()
290 {
291 	return home_filename(".ldapvi_history");
292 }
293 
294 void
read_ldapvi_history()295 read_ldapvi_history()
296 {
297 	char *filename = history_filename();
298 	using_history();
299 	if (!filename)
300 		return;
301 	if (read_history(filename) && errno != ENOENT)
302 		perror("Oops, couldn't read history");
303 	free(filename);
304 }
305 
306 void
write_ldapvi_history()307 write_ldapvi_history()
308 {
309 	char *filename = history_filename();
310 	if (!filename)
311 		return;
312 	if (write_history(filename))
313 		perror("Oops, couldn't write history");
314 	free(filename);
315 }
316 
317 char *
ldapvi_getline(char * prompt,char * value)318 ldapvi_getline(char *prompt, char *value)
319 {
320 	tdialog d;
321 	init_dialog(&d, DIALOG_DEFAULT, prompt, value);
322 	dialog(0, &d, 1, 0);
323 	return d.value ? d.value : xdup("");
324 }
325 
326 char *
get_password()327 get_password()
328 {
329 	tdialog d;
330 	init_dialog(&d, DIALOG_PASSWORD, "Password: ", "");
331 	dialog(0, &d, 1, 0);
332 	return d.value ? d.value : xdup("");
333 }
334 
335 static char *readline_default;
336 
337 static int
cb_set_readline_default()338 cb_set_readline_default()
339 {
340 	rl_insert_text(readline_default);
341 	return 0;
342 }
343 
344 void
display_password(void)345 display_password(void)
346 {
347 	int i;
348 	char *backup = xalloc(rl_end + 1);
349 	strncpy(backup, rl_line_buffer, rl_end);
350 	for (i = 0; i < rl_end; i++)
351 		rl_line_buffer[i] = '*';
352 	rl_redisplay();
353 	strncpy(rl_line_buffer, backup, rl_end);
354 }
355 
356 static char *
getline2(char * prompt,char * value,int password,int history)357 getline2(char *prompt, char *value, int password, int history)
358 {
359 	char *str;
360 
361 	if (password)
362 		rl_redisplay_function = display_password;
363 
364 	readline_default = value;
365 	rl_startup_hook = cb_set_readline_default;
366 	str = readline(prompt);
367 	rl_startup_hook = 0;
368 
369 	if (password)
370 		rl_redisplay_function = rl_redisplay;
371 
372 	if (str && *str && history)
373 		add_history(str);
374 	return str;
375 }
376 
377 void
init_dialog(tdialog * d,enum dialog_mode mode,char * prompt,char * value)378 init_dialog(tdialog *d, enum dialog_mode mode, char *prompt, char *value)
379 {
380 	d->mode = mode;
381 	d->prompt = prompt;
382 	d->value = value;
383 }
384 
385 char *
append(char * a,char * b)386 append(char *a, char *b)
387 {
388 	int k = strlen(a);
389 	char *result = xalloc(k + strlen(b) + 1);
390 	strcpy(result, a);
391 	strcpy(result + k, b);
392 	return result;
393 }
394 
395 void *
xalloc(size_t size)396 xalloc(size_t size)
397 {
398 	void *result = malloc(size);
399 	if (!result) {
400 		write(2, "\nmalloc error\n", sizeof("\nmalloc error\n") - 1);
401 		_exit(2);
402 	}
403 	return result;
404 }
405 
406 char *
xdup(char * str)407 xdup(char *str)
408 {
409 	char *result;
410 
411 	if (!str)
412 		return str;
413 	if (!(result = strdup(str))) {
414 		write(2, "\nstrdup error\n", sizeof("\nstrdup error\n") - 1);
415 		_exit(2);
416 	}
417 	return result;
418 }
419 
420 int
adjoin_str(GPtrArray * strs,char * str)421 adjoin_str(GPtrArray *strs, char *str)
422 {
423 	int i;
424 	for (i = 0; i < strs->len; i++)
425 		if (!strcmp(str, g_ptr_array_index(strs, i)))
426 			return -1;
427 	g_ptr_array_add(strs, str);
428 	return i;
429 }
430 
431 int
adjoin_ptr(GPtrArray * a,void * p)432 adjoin_ptr(GPtrArray *a, void *p)
433 {
434 	int i;
435 	for (i = 0; i < a->len; i++)
436 		if (g_ptr_array_index(a, i) == p)
437 			return -1;
438 	g_ptr_array_add(a, p);
439 	return i;
440 }
441 
442 void
dumb_dialog(tdialog * d,int n)443 dumb_dialog(tdialog *d, int n)
444 {
445 	GString *prompt = g_string_new("");
446 	int i;
447 
448 	for (i = 0; i < n; i++) {
449 		g_string_assign(prompt, d[i].prompt);
450 		g_string_append(prompt, ": ");
451 		switch (d[i].mode) {
452 		case DIALOG_DEFAULT:
453 			d[i].value = getline2(prompt->str, d[i].value, 0, 1);
454 			break;
455 		case DIALOG_PASSWORD:
456 			d[i].value = getline2(prompt->str, d[i].value, 1, 0);
457 			break;
458 		case DIALOG_CHALLENGE:
459 			printf("%s: %s\n", prompt->str, d[i].value);
460 			break;
461 		}
462 	}
463 	g_string_free(prompt, 1);
464 }
465 
466 enum dialog_rc {
467 	dialog_continue, dialog_done, dialog_goto, dialog_relative,
468 	dialog_help, dialog_clear
469 };
470 
471 static Keymap dialog_keymap = 0;
472 static Keymap dialog_empty_keymap = 0;
473 static enum dialog_rc dialog_action;
474 static int dialog_next;
475 
476 static int
cb_view_pre_input()477 cb_view_pre_input()
478 {
479 	rl_done = 1;
480 	return 0;
481 }
482 
483 static int
cb_dialog_done(int a,int b)484 cb_dialog_done(int a, int b)
485 {
486 	rl_done = 1;
487 	dialog_action = dialog_done;
488 	return 42;
489 }
490 
491 static int
cb_dialog_goto(int a,int b)492 cb_dialog_goto(int a, int b)
493 {
494 	rl_done = 1;
495 	dialog_action = dialog_goto;
496 	dialog_next = a - 1;
497 	return 42;
498 }
499 
500 static int
cb_dialog_prev(int a,int b)501 cb_dialog_prev(int a, int b)
502 {
503 	rl_done = 1;
504 	dialog_action = dialog_relative;
505 	dialog_next = - 1;
506 	return 42;
507 }
508 
509 static int
cb_dialog_next(int a,int b)510 cb_dialog_next(int a, int b)
511 {
512 	rl_done = 1;
513 	dialog_action = dialog_relative;
514 	dialog_next = 1;
515 	return 42;
516 }
517 
518 static int
cb_dialog_help(int a,int b)519 cb_dialog_help(int a, int b)
520 {
521 	rl_done = 1;
522 	dialog_action = dialog_help;
523 	return 42;
524 }
525 
526 static int
cb_dialog_clear(int a,int b)527 cb_dialog_clear(int a, int b)
528 {
529 	rl_done = 1;
530 	dialog_action = dialog_clear;
531 	return 42;
532 }
533 
534 #define DIALOG_HELP							\
535 "\nEdit the lines above using standard readline commands.\n"		\
536 "Use RET to edit each line in turn.\n"					\
537 "\n"									\
538 "Special keys:\n"							\
539 "  M-RET       Finish the dialog immediately.\n"			\
540 "  C-p         Go back to the previous line.\n"				\
541 "  C-n         Go to the next line (alias for RET).\n"			\
542 "  M-g         With numeric prefix, go to the specified line.\n"	\
543 "\n"									\
544 "Non-password lines are saved in the history.  Standard readline\n"	\
545 "bindings for history access include:\n"				\
546 "  C-r         Incremental search through history.\n"			\
547 "  <up>/<down> Previous/next history entry.\n"
548 
549 static void
dialog_rebuild(char * up,char * clreos,char * header,char ** prompts,tdialog * d,int n,int target,int help)550 dialog_rebuild(char *up, char *clreos,
551 	       char *header, char **prompts, tdialog *d, int n,
552 	       int target, int help)
553 {
554 	int i;
555 
556 	putp(clreos);
557 	if (header) {
558 		putchar('\n');
559 		fputs(header, stdout);
560 		putchar('\n');
561 		fputs("Type M-h for help on key bindings.", stdout);
562 		putchar('\n');
563 		putchar('\n');
564 	}
565 
566 	rl_pre_input_hook = cb_view_pre_input;
567 	for (i = 0; i < n; i++) {
568 		int passwordp = d[i].mode == DIALOG_PASSWORD;
569 		free(getline2(prompts[i], d[i].value, passwordp, 0));
570 		putchar('\n');
571 	}
572 	rl_pre_input_hook = 0;
573 
574 	if (help) {
575 		fputs(DIALOG_HELP, stdout);
576 		for (i = 0; DIALOG_HELP[i]; i++)
577 			if (DIALOG_HELP[i] == '\n')
578 				putp(up);
579 	}
580 
581 	for (i = 0; i < n - target; i++)
582 		putp(up);
583 }
584 
585 static Keymap
set_meta_keymap(Keymap keymap,Keymap meta_keymap)586 set_meta_keymap(Keymap keymap, Keymap meta_keymap)
587 {
588 	if (!meta_keymap)
589 		meta_keymap = rl_copy_keymap((Keymap) keymap[27].function);
590 	keymap[27].type = ISKMAP;
591 	keymap[27].function = (rl_command_func_t *) meta_keymap;
592 }
593 
594 
595 static void
init_dialog_keymap(Keymap keymap)596 init_dialog_keymap(Keymap keymap)
597 {
598 	Keymap meta_keymap = (Keymap) keymap[27].function;
599 	rl_bind_key_in_map('L' - '@', cb_dialog_clear, keymap);
600 	rl_bind_key_in_map('P' - '@', cb_dialog_prev, keymap);
601 	rl_bind_key_in_map('N' - '@', cb_dialog_next, keymap);
602 	rl_bind_key_in_map('\r', cb_dialog_done, meta_keymap);
603 	rl_bind_key_in_map('g', cb_dialog_goto, meta_keymap);
604 	rl_bind_key_in_map('h', cb_dialog_help, meta_keymap);
605 }
606 
607 
608 void
dialog(char * header,tdialog * d,int n,int start)609 dialog(char *header, tdialog *d, int n, int start)
610 {
611 	int i;
612 	char *up = tigetstr("cuu1");
613 	char *clreos = tigetstr("ed");
614 	char *clear = tigetstr("clear");
615 #if 0
616 	char *hsm = rl_variable_value("horizontal-scroll-mode");
617 #endif
618 	char *hsm = "off";
619 	Keymap original_keymap = rl_get_keymap();
620 	int max = 0;
621 	char **prompts;
622 
623 	if (n == 0)
624 		return;
625 
626 	if (invalidp(up) || invalidp(clreos) || invalidp(clear)) {
627 		puts("Dumb terminal.  Using fallback dialog.");
628 		dumb_dialog(d, n);
629 		return;
630 	}
631 
632 	if (!dialog_keymap) {
633 		rl_initialize();
634 		dialog_keymap = rl_copy_keymap(original_keymap);
635 		dialog_empty_keymap = rl_make_bare_keymap();
636 		set_meta_keymap(dialog_keymap, 0);
637 		set_meta_keymap(dialog_empty_keymap, rl_make_bare_keymap());
638 		init_dialog_keymap(dialog_keymap);
639 		init_dialog_keymap(dialog_empty_keymap);
640 	}
641 
642 	rl_variable_bind("horizontal-scroll-mode", "on");
643 	rl_inhibit_completion = 1; /* fixme */
644 
645 	for (i = 0; i < n; i++)
646 		max = MAX(max, strlen(d[i].prompt));
647 	prompts = xalloc(sizeof(char *) * n);
648 
649 	for (i = 0; i < n; i++) {
650 		char *prompt = d[i].prompt;
651 		int len = strlen(prompt);
652 		char *str = xalloc(max + 3);
653 		memset(str, ' ', max);
654 		strcpy(str + max - len, prompt);
655 		strcpy(str + max, ": ");
656 		prompts[i] = str;
657 
658 		if (d[i].value)
659 			d[i].value = xdup(d[i].value);
660 	}
661 
662 	dialog_rebuild(up, clreos, header, prompts, d, n, start, 0);
663 
664 	i = start;
665 	for (;;) {
666 		char *orig = d[i].value;
667 		int passwordp = d[i].mode == DIALOG_PASSWORD;
668 
669 		dialog_action = dialog_continue;
670 		if (d[i].mode == DIALOG_CHALLENGE)
671 			rl_set_keymap(dialog_empty_keymap);
672 		else
673 			rl_set_keymap(dialog_keymap);
674 		d[i].value = getline2(prompts[i], orig, passwordp, !passwordp);
675 		if (orig)
676 			free(orig);
677 
678 		switch (dialog_action) {
679 		case dialog_continue:
680 			dialog_next = i + 1;
681 			break;
682 		case dialog_clear: /* fall through */
683 		case dialog_help:
684 			dialog_next = i;
685 			break;
686 		case dialog_relative:
687 			dialog_next += i;
688 			/* fall through */
689 		case dialog_goto:
690 			if (dialog_next < 0 || dialog_next >= n)
691 				dialog_next = i;
692 			break;
693 		case dialog_done:
694 			dialog_next = n;
695 			break;
696 		}
697 
698 		if (dialog_action == dialog_clear)
699 			putp(clear);
700 		else {
701 			if (header)
702 				i += 4;
703 			if (dialog_action != dialog_continue)
704 				i--;
705 			do putp(up); while (i--);
706 		}
707 
708 		dialog_rebuild(up, clreos, header, prompts, d, n, dialog_next,
709 			       dialog_action == dialog_help);
710 		if (dialog_next >= n)
711 			break;
712 		i = dialog_next;
713 	}
714 
715 	for (i = 0; i < n; i++)
716 		free(prompts[i]);
717 	free(prompts);
718 
719 	rl_set_keymap(original_keymap);
720 	rl_variable_bind("horizontal-scroll-mode", hsm);
721 	rl_inhibit_completion = 0;
722 }
723