1 /*
2
3 /usr/src/ext2ed/main.c
4
5 A part of the extended file system 2 disk editor.
6
7 ------------
8 Main program
9 ------------
10
11 This file mostly contains:
12
13 1. A list of global variables used through the entire program.
14 2. The parser, which asks the command line from the user.
15 3. The dispatcher, which analyzes the command line and calls the appropriate handler function.
16 4. A command pattern matcher which is used along with the readline completion feature.
17 5. A function which tells the user that an internal error has occured.
18
19 First written on: March 30 1995
20
21 Copyright (C) 1995 Gadi Oxman
22
23 */
24
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <signal.h>
29
30 #ifdef HAVE_READLINE
31 #include <readline.h>
32 #include <history.h>
33 #endif
34
35 #ifdef HAVE_GETOPT_H
36 #include <getopt.h>
37 #else
38 extern int optind;
39 extern char *optarg;
40 #endif
41
42 #include "ext2ed.h"
43
44 /* Global variables */
45
46 /*
47
48 Configuration file options
49
50 The following variables will be set by init.c to the values selected in the user configuration file.
51 They are initialized below to some logical defaults.
52
53 */
54
55
56 char Ext2Descriptors [200]="ext2.descriptors"; /* The location of the ext2 filesystem object definition */
57 char AlternateDescriptors [200]=""; /* We allow the user to define additional structures */
58 char LogFile [200]="ext2ed.log"; /* The location of the log file - Each write will be logged there */
59 int LogChanges=1; /* 1 enables logging, 0 diables logging */
60 int AllowChanges=0; /* When set, the enablewrite command will fail */
61 int AllowMountedRead=0; /* Behavior when trying to open a mounted filesystem read-only */
62 int ForceExt2=0; /* When set, ext2 autodetection is overridden */
63 int DefaultBlockSize=1024;
64 unsigned long DefaultTotalBlocks=2097151;
65 unsigned long DefaultBlocksInGroup=8192; /* The default values are used when an ext2 filesystem is not */
66 int ForceDefault=0; /* detected, or ForceDefault is set */
67
68 char last_command_line [80]; /* A simple one command cache, in addition to the readline history */
69
70 char device_name [80]; /* The location of the filesystem */
71 FILE *device_handle=NULL; /* This is passed to the fopen / fread ... commands */
72 long device_offset; /* The current position in the filesystem */
73 /* Note that we have a 2 GB limitation */
74
75 int mounted=0; /* This is set when we find that the filesystem is mounted */
76
77 struct struct_commands general_commands,ext2_commands; /* Used to define the general and ext2 commands */
78 struct struct_descriptor *first_type,*last_type,*current_type; /* Used to access the double linked list */
79 struct struct_type_data type_data; /* The current data is sometimes stored here */
80 struct struct_file_system_info file_system_info; /* Essential information on the filesystem */
81 struct struct_file_info file_info,first_file_info; /* Used by file_com.c to access files */
82 struct struct_group_info group_info; /* Used by group_com.c */
83 struct struct_super_info super_info; /* Used by super_com.c */
84 struct struct_remember_lifo remember_lifo; /* A circular memory of objects */
85 struct struct_block_bitmap_info block_bitmap_info; /* Used by blockbitmap_com.c */
86 struct struct_inode_bitmap_info inode_bitmap_info; /* Used by inodebitmap_com.c */
87
88 int redraw_request=0; /* Is set by a signal handler to handle terminal */
89 /* screen size change. */
90
91
92 /*
93 * We just call the parser to get commands from the user. We quit when
94 * parser returns.
95 */
main(int argc,char ** argv)96 int main (int argc, char **argv)
97 {
98 int write_priv = 0;
99 int c;
100 char *buf;
101
102 if (!init ())
103 return (1);
104 while ((c = getopt (argc, argv, "w")) != EOF) {
105 switch (c) {
106 case 'w':
107 write_priv++;
108 break;
109 }
110 }
111 if (optind < argc) {
112 buf = malloc(strlen(argv[optind]) + 32);
113 if (!buf) {
114 fprintf(stderr, "Couldn't allocate filename buffer\n");
115 exit(1);
116 }
117 strcpy(buf, "set_device ");
118 strcat(buf, argv[optind]);
119 set_device(buf);
120 free(buf);
121 if (write_priv) {
122 wprintw (command_win,"\n");
123 enable_write("enable_write");
124 }
125 }
126 parser (); /* Get and parse user commands */
127 prepare_to_close(); /* Do some cleanup */
128 printf("Quitting ...\n");
129 return(0);
130 }
131
132
133 /*
134 * Read a character from the command window
135 */
command_read_key()136 int command_read_key()
137 {
138 int key = 0;
139
140 while (!key) {
141 if (redraw_request) {
142 redraw_all();
143 redraw_request=0;
144 }
145 key = wgetch(command_win);
146 switch (key) {
147 case 0x1A:
148 key = 0;
149 kill(getpid(), SIGTSTP);
150 break;
151
152 case KEY_NPAGE:
153 pgdn("");
154 refresh_command_win ();
155 break;
156
157 case KEY_PPAGE:
158 pgup("");
159 refresh_command_win ();
160 break;
161 case ERR:
162 key = 0;
163 break;
164
165 case KEY_BACKSPACE:
166 key = '\b';
167 }
168 if ((key < 32 && key != '\b' && key != '\n') ||
169 (key > 127))
170 key = 0;
171 }
172 return key;
173 }
174
175 #ifdef HAVE_READLINE
rl_getc_replacement(FILE * f)176 int rl_getc_replacement(FILE *f)
177 {
178 int key = command_read_key();
179
180 if (key == '\b') {
181 if (rl_point > 0)
182 wprintw(command_win, "\b \b");
183 } else
184 wprintw(command_win, "%c", key);
185 return key;
186 }
187
188 /*
189 * This function asks the user for a command and calls the dispatcher
190 * function, dispatch, to analyze it. We use the readline library
191 * function readline to read the command, hence all the usual readline
192 * keys are available. The new command is saved both in the
193 * readline's history and in our tiny one-command cache, so that only
194 * the enter key is needed to retype it.
195 */
parser(void)196 void parser (void)
197 {
198 char *ptr,command_line [80];
199 int quit=0;
200
201 #if 0
202 noecho();
203 cbreak();
204 keypad(command_win, 1);
205 wtimeout(command_win, 100);
206
207 rl_getc_function = rl_getc_replacement;
208 #endif
209
210 while (!quit) {
211 /* Terminal screen size has changed */
212 if (redraw_request) {
213 redraw_all();
214 redraw_request=0;
215 }
216
217 wmove (command_win,0,0);
218 wclrtoeol (command_win);
219 wprintw (command_win,"ext2ed > ");
220 refresh_command_win ();
221
222 /*
223 * The ncurses library optimizes cursor movement by
224 * keeping track of the cursor position. However, by
225 * using the readline library I'm breaking its
226 * assumptions. The double -1 arguments tell ncurses
227 * to disable cursor movement optimization this
228 * time.
229 */
230 mvcur (-1,-1,LINES-COMMAND_WIN_LINES,0);
231
232 /* echo (); */
233 ptr=readline ("ext2ed > ");
234 /* noecho (); */
235
236 /*
237 * Readline allocated the buffer - Copy the string
238 * and free the allocated buffer
239 * XXX WHY???
240 */
241 strcpy (command_line,ptr);
242 free (ptr);
243
244 if (*command_line != 0)
245 add_history (command_line);
246
247 /* If only enter was pressed, recall the last command */
248 if (*command_line==0)
249 strcpy (command_line,last_command_line);
250
251 /* Emulate readline's actions for ncurses */
252 mvcur (-1,-1,LINES-COMMAND_WIN_LINES,0);
253 werase (command_win);
254 wprintw (command_win,"ext2ed > ");
255 wprintw (command_win,command_line);
256 wprintw (command_win,"\n");
257 refresh_command_win ();
258
259 /* Save this command in our tiny cache */
260 strcpy (last_command_line,command_line);
261
262 /* And call dispatch to do the actual job */
263 quit=dispatch (command_line);
264 }
265 }
266 #else
read_line(char * foo)267 void read_line(char * foo) {
268 char * chptr = foo;
269 int ch;
270 int done = 0;
271
272 while (!done && (ch = command_read_key())) {
273 switch (ch) {
274 case '\n':
275 done = 1;
276 break;
277
278 case '\b':
279 if (chptr > foo) {
280 wprintw(command_win, "\b \b");
281 chptr--;
282 }
283 break;
284
285 default:
286 if (ch > 256)
287 break;
288 if (ch == '\n') break;
289 *chptr++ = ch;
290 wprintw(command_win, "%c", ch);
291 break;
292 }
293 }
294 *chptr = '\0';
295 }
296
parser(void)297 void parser (void)
298 {
299 char command_line [80];
300 int quit=0;
301
302 noecho();
303 cbreak();
304 wtimeout(command_win, 100);
305 keypad(command_win, 1);
306
307 while (!quit) {
308 /* Terminal screen size has changed */
309 if (redraw_request) {
310 redraw_all();
311 redraw_request=0;
312 }
313
314 wmove (command_win,0,0);wclrtoeol (command_win);
315
316 wmove(command_win, 0, 0);
317 wprintw(command_win, "ext2ed > ");
318 read_line(command_line);
319
320 /* If only enter was pressed, recall the last command */
321 if (*command_line==0)
322 strcpy (command_line,last_command_line);
323
324 mvcur (-1,-1,LINES-COMMAND_WIN_LINES + 1,0);
325
326 strcpy (last_command_line,command_line); /* Save this command in our tiny cache */
327
328 /* And call dispatch to do the actual job */
329 quit=dispatch (command_line);
330 }
331 }
332 #endif
333
334
335 /*
336 * This is a very important function. Its task is to recieve a command
337 * name and link it to a C function. There are three types of commands:
338 *
339 * 1. General commands - Always available and accessed through
340 * general_commands.
341 * 2. Ext2 specific commands - Available when editing an ext2
342 * filesystem, accessed through ext2_commands.
343 * 3. Type specific commands - Those are changing according to the
344 * current type. The global variable current_type points to the
345 * current object definition (of type struct_descriptor). In it, the
346 * struct_commands entry contains the type specific commands links.
347 *
348 * Overriding is an important feature - Much like in C++ : The same
349 * command name can dispatch to different functions. The overriding
350 * priority is 3,2,1; That is - A type specific command will always
351 * override a general command. This is used through the program to
352 * allow fine tuned operation.
353 *
354 * When an handling function is found, it is called along with the
355 * command line that was passed to us. The handling function is then
356 * free to interpert the arguments in its own style.
357 */
dispatch(char * command_line)358 int dispatch (char *command_line)
359 {
360 int i,found=0;
361
362 char command [80];
363
364 parse_word (command_line,command);
365
366 if (strcasecmp (command,"quit")==0) return (1);
367
368 /* 1. Search for type specific commands FIRST - Allows
369 overriding of a general command */
370
371 if (current_type != NULL)
372 for (i=0;
373 i<=current_type->type_commands.last_command && !found;
374 i++) {
375 if (strcasecmp (command,current_type->type_commands.names [i])==0) {
376 (*current_type->type_commands.callback [i]) (command_line);
377 found=1;
378 }
379 }
380
381 /* 2. Now search for ext2 filesystem general commands */
382
383 if (!found)
384 for (i=0;i<=ext2_commands.last_command && !found;i++) {
385 if (strcasecmp (command,ext2_commands.names [i])==0) {
386 (*ext2_commands.callback [i]) (command_line);
387 found=1;
388 }
389 }
390
391
392 /* 3. If not found, search the general commands */
393
394 if (!found)
395 for (i=0;i<=general_commands.last_command && !found;i++) {
396 if (strcasecmp (command,general_commands.names [i])==0) {
397 (*general_commands.callback [i]) (command_line);
398 found=1;
399 }
400 }
401
402 /* 4. If not found, issue an error message and return */
403
404 if (!found) {
405 wprintw (command_win,"Error: Unknown command\n");
406 refresh_command_win ();
407 }
408
409 return (0);
410 }
411
412
413 /*
414 *
415 * This function copies the next word in source to the variable dest,
416 * ignoring whitespaces. It returns a pointer to the next word in
417 * source. It is used to split the command line into command and arguments.
418 */
parse_word(char * source,char * dest)419 char *parse_word (char *source,char *dest)
420 {
421 char ch,*source_ptr,*target_ptr;
422
423 if (*source==0) {
424 *dest=0;
425 return (source);
426 };
427
428 source_ptr=source;target_ptr=dest;
429 do {
430 ch=*source_ptr++;
431 } while (! (ch>' ' && ch<='z') && ch!=0);
432
433 while (ch>' ' && ch<='z') {
434 *target_ptr++=ch;
435 ch=*source_ptr++;
436 }
437
438 *target_ptr=0;
439
440 source_ptr--;
441 do {
442 ch=*source_ptr++;
443 } while (! (ch>' ' && ch<='z') && ch!=0);
444
445 return (--source_ptr);
446 }
447
448 /*
449 * text is the partial command entered by the user; We assume that it
450 * is a part of a command - I didn't write code for smarter completion.
451 *
452 * The state variable is an index which tells us how many possible
453 * completions we already returned to readline.
454 *
455 * We return only one possible completion or (char *) NULL if there
456 * are no more completions. This function will be called by readline
457 * over and over until we tell it to stop.
458 *
459 * While scanning for possible completions, we use the same priority
460 * definition which was used in dispatch.
461 */
462 #if HAVE_READLINE
complete_command(char * text,int state)463 char *complete_command (char *text,int state)
464 {
465 int state_index=-1;
466 int i,len;
467
468 len=strlen (text);
469
470 /* Is the command type specific ? */
471
472 if (current_type != NULL)
473 for (i=0;i<=current_type->type_commands.last_command;i++) {
474 if (strncmp (current_type->type_commands.names [i],text,len)==0) {
475 state_index++;
476 if (state==state_index) {
477 return (dupstr (current_type->type_commands.names [i]));
478 }
479 }
480 }
481
482 /* No, pehaps ext2 specific command then ? */
483
484 for (i=0;i<=ext2_commands.last_command;i++) {
485 if (strncmp (ext2_commands.names [i],text,len)==0) {
486 state_index++;
487 if (state==state_index)
488 return (dupstr (ext2_commands.names [i]));
489 }
490 }
491
492
493 /* Check for a general command */
494
495 for (i=0;i<=general_commands.last_command;i++) {
496 if (strncmp (general_commands.names [i],text,len)==0) {
497 state_index++;
498 if (state==state_index)
499 return (dupstr (general_commands.names [i]));
500 }
501 }
502
503 /* quit is handled differently */
504
505 if (strncmp ("quit",text,len)==0) {
506 state_index++;
507 if (state==state_index)
508 return (dupstr ("quit"));
509 }
510
511 /* No more completions */
512
513 return ((char *) NULL);
514 }
515 #endif
516
517
518 /*
519 * Nothing special - Just allocates enough space and copy the string.
520 */
dupstr(char * src)521 char *dupstr (char *src)
522 {
523 char *ptr;
524
525 ptr=(char *) malloc (strlen (src)+1);
526 strcpy (ptr,src);
527 return (ptr);
528 }
529
530 #ifdef DEBUG
531 /*
532 * This function reports an internal error. It is almost not used. One
533 * place in which I do check for internal errors is disk.c.
534 *
535 * We just report the error, and try to continue ...
536 */
internal_error(char * description,char * source_name,char * function_name)537 void internal_error (char *description,char *source_name,char *function_name)
538 {
539 wprintw (command_win,"Internal error - Found by source: %s.c , function: %s\n",source_name,function_name);
540 wprintw (command_win,"\t%s\n",description);
541 wprintw (command_win,"Press enter to (hopefully) continue\n");
542 refresh_command_win ();getch ();werase (command_win);
543 }
544
545 #endif
546