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