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