1 /* $Id: interface.c,v 1.9 2001/05/30 15:47:03 harbourn Exp $
2 * This module handles user interface processing (via commands)
3 */
4 #include <stdio.h>
5 #include <sys/types.h>
6 #include <sys/file.h>
7 #include <sys/stat.h>
8 #include <sys/errno.h>
9 #include <sys/wait.h>
10 #include <string.h>
11 #include <assert.h>
12 #include <stdlib.h>
13 #include <fnmatch.h>
14 #include <unistd.h>
15 #include <errno.h>
16 #include "dirtree.h"
17 #include "util.h"
18 #include "fat.h"
19 #include "vbr.h"
20 #include "output.h"
21 #include "recovery.h"
22 #include "fatback.h"
23 #include "interface.h"
24 #include "interface_data.h"
25 #include "vars.h"
26
27 /* to change to libreadline, uncomment the
28 * #include and comment out the prototype */
29
30 /*#include <readline/readline.h>*/
31 static char *readline(char *);
32 static int ending(char *);
33
34 typedef char* (*cmdline_hook_t)(char *);
35
36 static char *stripwhite(char *);
37 static char *stripcomments(char *);
38 static char *strippipe(char *);
39 static char *pipe_scan(char *);
40 static int whitespace(char);
41
42 static command_t *find_command(char *);
43 static char **split_line(char *);
44 static char *cmdline_car(char *, int *);
45
46 /*
47 * Display a menu of the possible partitions,
48 * and prompt the user as to which one to recover.
49 * let them undelete that partition, and keep looping
50 * until we recieve the stop code.
51 */
partition_menu(int num_parts,int flags)52 void partition_menu(int num_parts, int flags)
53 {
54 int i;
55 unsigned part;
56 char *choice;
57
58 /* if only one partition exists, dont bother with the menu */
59 if (num_parts == 1) {
60 display(VERBOSE, "Only one partition detected: Entering single partition mode\n");
61 undel_partition(0, flags);
62 return;
63 }
64
65 for (;;) {
66 display(NORMAL, "Please select one of the following partitions:");
67 for (i = 0; i < num_parts; i++)
68 display(NORMAL, " %d", i);
69 display(NORMAL, "\n");
70 choice = readline(">");
71 display(LOGONLY, "\n");
72 part = atoi(choice);
73 free(choice);
74 if (part > num_parts)
75 display(NORMAL, "Invalid partition number\n");
76 else if (undel_partition(part, flags) == STOPCODE_QUIT)
77 return;
78 }
79 }
80
81 /*
82 * Initialize variables in the interface.
83 * this should be called each time a new partition is to be edited
84 */
interface_init(dirent_t * tree,clust_t * clust_array,vbr_t myvbr)85 void interface_init(dirent_t *tree, clust_t *clust_array, vbr_t myvbr)
86 {
87 assert(tree && clust_array && myvbr);
88 stop_code = 0;
89 cwd = root_dir = tree;
90 clusts = clust_array;
91 vbr = myvbr;
92 }
93
94 /*
95 * Prompt the user for a command and process that command.
96 */
process_commands(void)97 int process_commands(void)
98 {
99 char *line, *s;
100
101 while (!stop_code) {
102 fbvar_t *prompt_var;
103 char *tmp_prompt, *prompt;
104 char *pipe_command;
105 FILE *tmp_pipe;
106 errno = 0;
107 prompt_var = get_fbvar("prompt");
108 tmp_prompt = prompt_var->val.sval;
109 free(prompt_var);
110 prompt = emalloc(strlen(tmp_prompt) + 2);
111 strcpy(prompt, tmp_prompt);
112 prompt[strlen(tmp_prompt)] = ' ';
113 prompt[strlen(tmp_prompt) + 1] = '\0';
114
115 display(LOGONLY, "%s", prompt);
116 line = readline(prompt ? prompt : "> ");
117 free(prompt);
118 if (!line)
119 break;
120 if (pipe_command = pipe_scan(line)) {
121 if ((tmp_pipe = popen(pipe_command, "w")) == NULL) {
122 perror("cannot create pipe");
123 break;
124 }
125 set_ostream(tmp_pipe);
126 }
127 exec_line(line);
128 free(line);
129 if (pipe_command) {
130 free(pipe_command);
131 pclose(tmp_pipe);
132 reset_ostream();
133 }
134 }
135 return stop_code;
136 }
137
138 /*
139 * Execute a command as specified by argument.
140 */
exec_line(char * line)141 void exec_line(char *line)
142 {
143 command_t *command;
144 char *newline;
145 char **argv;
146 int argc, i;
147 cmdline_hook_t cmdline_hooks[] =
148 {
149 stripcomments,
150 strippipe,
151 stripwhite,
152 NULL
153 };
154
155 assert(line);
156
157 /* skip the line if it is a comment */
158 if (line[0] == '#')
159 return;
160
161 newline = strdup(line);
162 /* log this line to the audit log. */
163 display(LOGONLY, "%s\n", line);
164
165 /* Apply the command line hooks */
166 for (i = 0; cmdline_hooks[i]; i++) {
167 char *tmp = (*cmdline_hooks[i])(newline);
168 free(newline);
169 newline = tmp;
170 }
171
172 /* split the line into an argv[] array */
173 if (!(argv = split_line(newline)))
174 return;
175 /* make argc the number of argv elements */
176 for (argc = 0; argv[argc]; argc++);
177
178 /* look up the command in the command table */
179 command = find_command(argv[0]);
180 if (!command) {
181 display(NORMAL, "Invalid command\n");
182 return;
183 }
184 (*(command->func))(argc, argv); /* run the command! */
185
186 /* free newly split command line */
187 for (i = 0; i < argc; i++)
188 free(argv[i]);
189 free(argv);
190 }
191
192 /*
193 * Concatenate an array of strings.
194 * Works the opposite of splitline.
195 */
argvcat(char * argv[])196 char *argvcat(char *argv[])
197 {
198 char *retval = NULL;
199 int i;
200
201 /* known bug: this algorithm leaves a trailing space at the
202 * end of each line it creates. */
203
204 for (i = 0; argv[i]; i++) {
205 char *tmp = retval;
206 int retval_len;
207 retval_len = (retval ? strlen(retval) : 0) + strlen(argv[i]) + 2;
208 retval = emalloc(retval_len);
209 *retval = '\0';
210 if (tmp)
211 strcpy(retval, tmp);
212 strcat(retval, argv[i]);
213 /* terminate each string with a space and a null */
214 retval[retval_len - 2] = ' ';
215 retval[retval_len - 1] = '\0';
216 if (tmp)
217 free(tmp);
218 }
219 return retval;
220 }
221
222 /*
223 * Lookup a command in the command table
224 */
find_command(char * name)225 static command_t *find_command(char *name)
226 {
227 int i;
228
229 /* the names of commands are simply strings, so just
230 * loop over the commands[] array strcmp'ing the names
231 */
232
233 if (!name)
234 return NULL;
235 for (i = 0; commands[i].name; i++) {
236 if (strcmp(name, commands[i].name) == 0)
237 return &commands[i];
238 }
239 return NULL;
240 }
241
242 /*
243 * Strip the white space from the ends of a string
244 */
stripwhite(char * string)245 static char *stripwhite(char *string)
246 {
247 char *retval;
248 int i, head_ws, tail_ws;
249 int retvallen, stringlen;
250
251 assert(string);
252 stringlen = strlen(string);
253
254 if (stringlen == 0)
255 return strdup(string);
256
257 /* count the initial whitespace */
258 for (i = 0; whitespace(string[i]); i++)
259 ;
260 head_ws = i;
261
262 /* count the amount of tail whitespace */
263 for (i = stringlen - 1; (i >= 0) && whitespace(string[i]); i--)
264 ;
265 tail_ws = stringlen - (i + 1);
266 retvallen = stringlen - (head_ws + tail_ws);
267
268 /* create the new string */
269 retval = emalloc(retvallen + 1);
270 strncpy(retval, &string[head_ws], retvallen);
271 retval[retvallen] = '\0';
272
273 return retval;
274 }
275
276 /*
277 * Strip out every thing on a line after
278 * the comment character ('#')
279 */
stripcomments(char * string)280 static char *stripcomments(char *string)
281 {
282 int i;
283 char *retval;
284
285 assert(string);
286 /* Find the comment (if any) on the line */
287 for (i = 0; string[i] && (string[i] != '#'); i++)
288 ;
289 retval = emalloc(i + 1);
290 strncpy(retval, string, i);
291 retval[i] = '\0';
292
293 return retval;
294 }
295
296 /*
297 * Look up a file in a directory tree based on a name
298 */
find_in_tree(dirent_t * dir,dirent_t * entry,char * name)299 dirent_t *find_in_tree(dirent_t *dir, dirent_t *entry, char *name)
300 {
301 char *entry_name, *remainder;
302 dirent_t *ent;
303
304 /* this funciton works by recursively breaking apart a file name
305 * into its layers of directories.
306 */
307
308 assert(dir && name);
309
310 /* find the first part of the file name */
311 entry_name = fn_car(name);
312 remainder = fn_cdr(name);
313 if (!entry_name) {
314 if (name[0] == delim)
315 return root_dir;
316 else
317 return NULL;
318 }
319
320 if (strcmp(entry_name, ".") == 0) {
321 free(entry_name);
322 if (!remainder)
323 return dir;
324 else
325 return find_in_tree(dir, dir->child, remainder);
326 }
327
328 if (strcmp(entry_name, "..") == 0) {
329 free(entry_name);
330 if (!remainder)
331 return dir->parent;
332 else
333 return find_in_tree(dir->parent, dir->parent->child, remainder);
334 }
335
336 if (!entry)
337 return NULL;
338
339 /* find an entry in the current directory that matches entry_name */
340 for (ent = entry; ent; ent = ent->next) {
341 dirent_t *matched_ent = NULL;
342 int match, match_lfn;
343
344 match = fnmatch(entry_name, ent->filename, 0);
345 if (ent->lfn)
346 match_lfn = fnmatch(entry_name, ent->lfn, 0);
347 else
348 match_lfn = FNM_NOMATCH;
349 if (match == 0 || match_lfn == 0) {
350 if (!remainder) {
351 free(entry_name);
352 return ent;
353 } else if (ent->attrs & ATTR_DIR) {
354 matched_ent = find_in_tree(ent, ent->child, remainder);
355 if (matched_ent) {
356 free(entry_name);
357 free(remainder);
358 return matched_ent;
359 }
360 }
361 }
362 }
363 free(entry_name);
364 if (remainder)
365 free(remainder);
366 return NULL;
367 }
368
369 /*
370 * givin an array of strings (probably from an argv[])
371 * find all the files that match, put them into a linked
372 * list and return them.
373 */
find_files(int num,char * strings[])374 entlist_t *find_files(int num, char *strings[])
375 {
376 unsigned i;
377 entlist_t *list_head = NULL, *list_tail = NULL;
378
379 /* loop over all arguments */
380 for (i = 0; i < num; i++) {
381 /* find all entries matching that pattern */
382 dirent_t *ent, *next = cwd->child;
383 int found = 0;
384 while (next && (ent = find_in_tree(cwd, next, strings[i]))) {
385 entlist_t *tmp = emalloc(sizeof *tmp);
386 found++;
387 tmp->next = NULL;
388 tmp->ent = ent;
389 if (!list_head)
390 list_head = tmp;
391 else
392 list_tail->next = tmp;
393 list_tail = tmp;
394 next = ent->next;
395 }
396 }
397 return list_head;
398 }
399
400 /*
401 * Extract the first piece of a file name.
402 * ("car" comes from the lisp primitive car, which
403 * means take the first element of a list.)
404 */
fn_car(char * name)405 char *fn_car(char *name)
406 {
407 int i=0, start, length;
408 char *retval;
409
410 assert(name);
411 /* first we find the length of the first part */
412 if (name[0] == delim)
413 i++;
414 start = i;
415 while (name[i] != '\0' && name[i] != delim)
416 i++;
417 length = i - start;
418
419 /* allocate space for our new string */
420 if (length == 0)
421 return NULL;
422 retval = emalloc(length + 1);
423
424 /* copy the fragment into the new string */
425 strncpy(retval, &name[start], length);
426 retval[length] = '\0';
427 return retval;
428 }
429
430 /* Extract all but the first piece of a file name.
431 * (similar to "car", "cdr" is taken from lisp as
432 * well, it means take all but the first element of
433 * a list.)
434 */
fn_cdr(char * name)435 char *fn_cdr(char *name)
436 {
437 int i=0, start, length;
438 char *retval;
439
440 assert(name);
441 /* find the length of the remainder */
442 if (name[0] == delim)
443 i++;
444 while (name[i] != '\0' && name[i] != delim)
445 i++;
446 start = i + 1; /* increment past the delimeter */
447 if (name[i] == '\0')
448 return NULL;
449 while (name[i] != '\0')
450 i++;
451 length = i - start;
452
453 /* allocate space for our new string */
454 if (length == 0)
455 return NULL;
456 retval = emalloc(length + 1);
457
458 /* copy the remainder into the new string */
459 strncpy(retval, &name[start], length);
460 retval[length] = '\0';
461 return retval;
462 }
463
464 /*
465 * Take all but the last portion of a file name.
466 * (There is no lisp primitive for rcdr, I made it up.
467 */
fn_rcdr(char * name)468 char *fn_rcdr(char *name)
469 {
470 int i, name_strlen;
471 char *retval;
472
473 assert(name);
474
475 /* calculate the length of the name string */
476 name_strlen = strlen(name);
477 if (!name_strlen)
478 return NULL;
479 /* position the index at the end of the string */
480 i = name_strlen - 1;
481
482 /* back up to before the delimeter, if any */
483 if (name[name_strlen - 1] == delim)
484 i--;
485 if (i < 0)
486 return NULL;
487 /* step backwards through the string until we
488 * find a delimeter, or we hit the beginning */
489 while ((i >= 0) && (name[i] != delim))
490 i--;
491 /* place all the data up to the index into a new string */
492 if (i < 0)
493 return NULL;
494 retval = emalloc(i + 2);
495 strncpy(retval, name, i + 1);
496 return retval;
497 }
498
499 /*
500 * Take a string delimeted by whitespace, and form
501 * it into an array of strings.
502 */
split_line(char * line)503 static char **split_line(char *line)
504 {
505 int i, total = 0;
506 char **list = NULL;
507
508 for (i = 0; line[i] != '\0'; ) {
509 char *word = cmdline_car(line, &i);
510 if (word) {
511 list = erealloc(list, (++total + 1) * sizeof list);
512 list[total - 1] = word;
513 list[total] = NULL;
514 }
515 }
516 return list;
517 }
518
519 /*
520 * Get the first datum in a command line
521 */
cmdline_car(char * line,int * index)522 static char *cmdline_car(char *line, int *index)
523 {
524 int i, j = 0, begin, end, quote_count = 0;
525 char *retval;
526
527 /* skip over leading whitespace */
528 for (i = *index; whitespace(line[i]); i++)
529 ;
530 begin = i;
531 /* now count the number of char's in word */
532 while (line[i] != '\0' && !whitespace(line[i])) {
533 /* skip over anything enclosed in double quotes */
534 if (line[i] == '\"') {
535 quote_count++;
536 for (i++; line[i] && line[i] != '\"'; i++);
537 if (line[i] == '\0') {
538 display(NORMAL, "Error: unfinished quote\n");
539 return NULL;
540 } else if (line[i] == '\"')
541 quote_count++;
542 }
543 i++;
544 }
545 /* determine how much space will be needed to hold our
546 * new string */
547 end = i;
548 if ((end - begin == 0) || (end - begin - quote_count == 0))
549 return NULL;
550 retval = emalloc(end - begin - quote_count + 1);
551 /* copy over the string */
552 i = begin;
553 while(i < end) {
554 if (line[i] != '\"')
555 retval[j++] = line[i];
556 i++;
557 }
558 retval[j] = '\0';
559 *index = i;
560 return retval;
561 }
562
563 /*
564 * Strip out every thing on a line after
565 * the pipe character ('|').
566 */
strippipe(char * string)567 static char *strippipe(char *string)
568 {
569 int i;
570 char *retval;
571
572 assert(string);
573
574 for (i = 0; string[i] && (string[i] != '|'); i++)
575 ;
576 retval = emalloc(i + 1);
577 strncpy(retval, string, i);
578 retval[i] = '\0';
579
580 return retval;
581 }
582
583 /*
584 * Find and return the remainder of a line
585 * of ther the pipe ('|') character
586 */
pipe_scan(char * line)587 static char *pipe_scan(char *line)
588 {
589 int i;
590 char *retval;
591
592 for (i = 0; line[i] && line[i] != '|'; i++)
593 ;
594 if (!i || i == strlen(line))
595 return NULL;
596 retval = emalloc(strlen(line) - i + 1);
597 strcpy(retval, line + i + 1);
598
599 return retval;
600 }
601
602 /*
603 * this is a mock readline funciton. if libreadline is
604 * ever added, this will need to be removed.
605 */
606 #define FBRL_BUFLEN 256
readline(char * prompt)607 static char *readline(char *prompt)
608 {
609 struct textlist_s {
610 char *text;
611 struct textlist_s *next;
612 };
613 char buffer[FBRL_BUFLEN];
614 char *retval, *rtmp;
615 size_t total_len = 0;
616 struct textlist_s *list_head = NULL, *list_tail = NULL, *tmp;
617
618 printf("%s", prompt);
619
620 /* read all input into a list of buffers */
621 do {
622 tmp = emalloc(sizeof *tmp);
623 fgets(buffer, FBRL_BUFLEN, stdin);
624 tmp->text = strdup(buffer);
625 tmp->next = NULL;
626 if (!list_head)
627 list_head = tmp;
628 if (list_tail)
629 list_tail->next = tmp;
630 list_tail = tmp;
631 } while (!ending(tmp->text));
632
633 /* combine the list of buffers into
634 * a single string
635 */
636 /* first calculate to total length. */
637 for (tmp = list_head; tmp; tmp = tmp->next)
638 total_len += tmp->next ? FBRL_BUFLEN - 1 : strlen(tmp->text) + 1;
639 /* now create a single buffer */
640 if (!total_len)
641 return NULL;
642 retval = emalloc(total_len);
643 for (rtmp = retval, tmp = list_head; tmp; tmp = tmp->next, rtmp += FBRL_BUFLEN - 1)
644 strcpy(rtmp, tmp->text);
645
646 return retval;
647 }
648
649 /*
650 * Determine if there exists a '\n' in the given buffer
651 * also, convert any newlines to \0's if found.
652 */
ending(char * buffer)653 static int ending(char *buffer)
654 {
655 while (*buffer) {
656 if (*buffer == '\n') {
657 *buffer = '\0';
658 return 1;
659 }
660 buffer++;
661 }
662 return 0;
663 }
664
665 /* remove this when libncurses is added */
whitespace(char x)666 static int whitespace(char x)
667 {
668 return (x == ' ' || x == '\t');
669 }
670