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 */ 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 */ 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 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 */ 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 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 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 */ 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 */ 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 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 */ 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 */ 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