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