| /* |
| |
| /usr/src/ext2ed/main.c |
| |
| A part of the extended file system 2 disk editor. |
| |
| ------------ |
| Main program |
| ------------ |
| |
| This file mostly contains: |
| |
| 1. A list of global variables used through the entire program. |
| 2. The parser, which asks the command line from the user. |
| 3. The dispatcher, which analyzes the command line and calls the appropriate handler function. |
| 4. A command pattern matcher which is used along with the readline completion feature. |
| 5. A function which tells the user that an internal error has occured. |
| |
| First written on: March 30 1995 |
| |
| Copyright (C) 1995 Gadi Oxman |
| |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <signal.h> |
| |
| #ifdef HAVE_READLINE |
| #include <readline.h> |
| #include <history.h> |
| #endif |
| |
| #ifdef HAVE_GETOPT_H |
| #include <getopt.h> |
| #else |
| extern int optind; |
| extern char *optarg; |
| #endif |
| |
| #include "ext2ed.h" |
| |
| /* Global variables */ |
| |
| /* |
| |
| Configuration file options |
| |
| The following variables will be set by init.c to the values selected in the user configuration file. |
| They are initialized below to some logical defaults. |
| |
| */ |
| |
| |
| char Ext2Descriptors [200]="ext2.descriptors"; /* The location of the ext2 filesystem object definition */ |
| char AlternateDescriptors [200]=""; /* We allow the user to define additional structures */ |
| char LogFile [200]="ext2ed.log"; /* The location of the log file - Each write will be logged there */ |
| int LogChanges=1; /* 1 enables logging, 0 diables logging */ |
| int AllowChanges=0; /* When set, the enablewrite command will fail */ |
| int AllowMountedRead=0; /* Behavior when trying to open a mounted filesystem read-only */ |
| int ForceExt2=0; /* When set, ext2 autodetection is overridden */ |
| int DefaultBlockSize=1024; |
| unsigned long DefaultTotalBlocks=2097151; |
| unsigned long DefaultBlocksInGroup=8192; /* The default values are used when an ext2 filesystem is not */ |
| int ForceDefault=0; /* detected, or ForceDefault is set */ |
| |
| char last_command_line [80]; /* A simple one command cache, in addition to the readline history */ |
| |
| char device_name [80]; /* The location of the filesystem */ |
| FILE *device_handle=NULL; /* This is passed to the fopen / fread ... commands */ |
| long device_offset; /* The current position in the filesystem */ |
| /* Note that we have a 2 GB limitation */ |
| |
| int mounted=0; /* This is set when we find that the filesystem is mounted */ |
| |
| struct struct_commands general_commands,ext2_commands; /* Used to define the general and ext2 commands */ |
| struct struct_descriptor *first_type,*last_type,*current_type; /* Used to access the double linked list */ |
| struct struct_type_data type_data; /* The current data is sometimes stored here */ |
| struct struct_file_system_info file_system_info; /* Essential information on the filesystem */ |
| struct struct_file_info file_info,first_file_info; /* Used by file_com.c to access files */ |
| struct struct_group_info group_info; /* Used by group_com.c */ |
| struct struct_super_info super_info; /* Used by super_com.c */ |
| struct struct_remember_lifo remember_lifo; /* A circular memory of objects */ |
| struct struct_block_bitmap_info block_bitmap_info; /* Used by blockbitmap_com.c */ |
| struct struct_inode_bitmap_info inode_bitmap_info; /* Used by inodebitmap_com.c */ |
| |
| int redraw_request=0; /* Is set by a signal handler to handle terminal */ |
| /* screen size change. */ |
| |
| |
| /* |
| * We just call the parser to get commands from the user. We quit when |
| * parser returns. |
| */ |
| int main (int argc, char **argv) |
| { |
| int write_priv = 0; |
| int c; |
| char *buf; |
| |
| if (!init ()) |
| return (1); |
| while ((c = getopt (argc, argv, "w")) != EOF) { |
| switch (c) { |
| case 'w': |
| write_priv++; |
| break; |
| } |
| } |
| if (optind < argc) { |
| buf = malloc(strlen(argv[optind]) + 32); |
| if (!buf) { |
| fprintf(stderr, "Couldn't allocate filename buffer\n"); |
| exit(1); |
| } |
| strcpy(buf, "set_device "); |
| strcat(buf, argv[optind]); |
| set_device(buf); |
| free(buf); |
| if (write_priv) { |
| wprintw (command_win,"\n"); |
| enable_write("enable_write"); |
| } |
| } |
| parser (); /* Get and parse user commands */ |
| prepare_to_close(); /* Do some cleanup */ |
| printf("Quitting ...\n"); |
| return(0); |
| } |
| |
| |
| /* |
| * Read a character from the command window |
| */ |
| int command_read_key() |
| { |
| int key = 0; |
| |
| while (!key) { |
| if (redraw_request) { |
| redraw_all(); |
| redraw_request=0; |
| } |
| key = wgetch(command_win); |
| switch (key) { |
| case 0x1A: |
| key = 0; |
| kill(getpid(), SIGTSTP); |
| break; |
| |
| case KEY_NPAGE: |
| pgdn(""); |
| refresh_command_win (); |
| break; |
| |
| case KEY_PPAGE: |
| pgup(""); |
| refresh_command_win (); |
| break; |
| case ERR: |
| key = 0; |
| break; |
| |
| case KEY_BACKSPACE: |
| key = '\b'; |
| } |
| if ((key < 32 && key != '\b' && key != '\n') || |
| (key > 127)) |
| key = 0; |
| } |
| return key; |
| } |
| |
| #ifdef HAVE_READLINE |
| int rl_getc_replacement(FILE *f) |
| { |
| int key = command_read_key(); |
| |
| if (key == '\b') { |
| if (rl_point > 0) |
| wprintw(command_win, "\b \b"); |
| } else |
| wprintw(command_win, "%c", key); |
| return key; |
| } |
| |
| /* |
| * This function asks the user for a command and calls the dispatcher |
| * function, dispatch, to analyze it. We use the readline library |
| * function readline to read the command, hence all the usual readline |
| * keys are available. The new command is saved both in the |
| * readline's history and in our tiny one-command cache, so that only |
| * the enter key is needed to retype it. |
| */ |
| void parser (void) |
| { |
| char *ptr,command_line [80]; |
| int quit=0; |
| |
| #if 0 |
| noecho(); |
| cbreak(); |
| keypad(command_win, 1); |
| wtimeout(command_win, 100); |
| |
| rl_getc_function = rl_getc_replacement; |
| #endif |
| |
| while (!quit) { |
| /* Terminal screen size has changed */ |
| if (redraw_request) { |
| redraw_all(); |
| redraw_request=0; |
| } |
| |
| wmove (command_win,0,0); |
| wclrtoeol (command_win); |
| wprintw (command_win,"ext2ed > "); |
| refresh_command_win (); |
| |
| /* |
| * The ncurses library optimizes cursor movement by |
| * keeping track of the cursor position. However, by |
| * using the readline library I'm breaking its |
| * assumptions. The double -1 arguments tell ncurses |
| * to disable cursor movement optimization this |
| * time. |
| */ |
| mvcur (-1,-1,LINES-COMMAND_WIN_LINES,0); |
| |
| /* echo (); */ |
| ptr=readline ("ext2ed > "); |
| /* noecho (); */ |
| |
| /* |
| * Readline allocated the buffer - Copy the string |
| * and free the allocated buffer |
| * XXX WHY??? |
| */ |
| strcpy (command_line,ptr); |
| free (ptr); |
| |
| if (*command_line != 0) |
| add_history (command_line); |
| |
| /* If only enter was pressed, recall the last command */ |
| if (*command_line==0) |
| strcpy (command_line,last_command_line); |
| |
| /* Emulate readline's actions for ncurses */ |
| mvcur (-1,-1,LINES-COMMAND_WIN_LINES,0); |
| werase (command_win); |
| wprintw (command_win,"ext2ed > "); |
| wprintw (command_win,command_line); |
| wprintw (command_win,"\n"); |
| refresh_command_win (); |
| |
| /* Save this command in our tiny cache */ |
| strcpy (last_command_line,command_line); |
| |
| /* And call dispatch to do the actual job */ |
| quit=dispatch (command_line); |
| } |
| } |
| #else |
| void read_line(char * foo) { |
| char * chptr = foo; |
| int ch; |
| int done = 0; |
| |
| while (!done && (ch = command_read_key())) { |
| switch (ch) { |
| case '\n': |
| done = 1; |
| break; |
| |
| case '\b': |
| if (chptr > foo) { |
| wprintw(command_win, "\b \b"); |
| chptr--; |
| } |
| break; |
| |
| default: |
| if (ch > 256) |
| break; |
| if (ch == '\n') break; |
| *chptr++ = ch; |
| wprintw(command_win, "%c", ch); |
| break; |
| } |
| } |
| *chptr = '\0'; |
| } |
| |
| void parser (void) |
| { |
| char command_line [80]; |
| int quit=0; |
| |
| noecho(); |
| cbreak(); |
| wtimeout(command_win, 100); |
| keypad(command_win, 1); |
| |
| while (!quit) { |
| /* Terminal screen size has changed */ |
| if (redraw_request) { |
| redraw_all(); |
| redraw_request=0; |
| } |
| |
| wmove (command_win,0,0);wclrtoeol (command_win); |
| |
| wmove(command_win, 0, 0); |
| wprintw(command_win, "ext2ed > "); |
| read_line(command_line); |
| |
| /* If only enter was pressed, recall the last command */ |
| if (*command_line==0) |
| strcpy (command_line,last_command_line); |
| |
| mvcur (-1,-1,LINES-COMMAND_WIN_LINES + 1,0); |
| |
| strcpy (last_command_line,command_line); /* Save this command in our tiny cache */ |
| |
| /* And call dispatch to do the actual job */ |
| quit=dispatch (command_line); |
| } |
| } |
| #endif |
| |
| |
| /* |
| * This is a very important function. Its task is to recieve a command |
| * name and link it to a C function. There are three types of commands: |
| * |
| * 1. General commands - Always available and accessed through |
| * general_commands. |
| * 2. Ext2 specific commands - Available when editing an ext2 |
| * filesystem, accessed through ext2_commands. |
| * 3. Type specific commands - Those are changing according to the |
| * current type. The global variable current_type points to the |
| * current object definition (of type struct_descriptor). In it, the |
| * struct_commands entry contains the type specific commands links. |
| * |
| * Overriding is an important feature - Much like in C++ : The same |
| * command name can dispatch to different functions. The overriding |
| * priority is 3,2,1; That is - A type specific command will always |
| * override a general command. This is used through the program to |
| * allow fine tuned operation. |
| * |
| * When an handling function is found, it is called along with the |
| * command line that was passed to us. The handling function is then |
| * free to interpert the arguments in its own style. |
| */ |
| int dispatch (char *command_line) |
| { |
| int i,found=0; |
| |
| char command [80]; |
| |
| parse_word (command_line,command); |
| |
| if (strcasecmp (command,"quit")==0) return (1); |
| |
| /* 1. Search for type specific commands FIRST - Allows |
| overriding of a general command */ |
| |
| if (current_type != NULL) |
| for (i=0; |
| i<=current_type->type_commands.last_command && !found; |
| i++) { |
| if (strcasecmp (command,current_type->type_commands.names [i])==0) { |
| (*current_type->type_commands.callback [i]) (command_line); |
| found=1; |
| } |
| } |
| |
| /* 2. Now search for ext2 filesystem general commands */ |
| |
| if (!found) |
| for (i=0;i<=ext2_commands.last_command && !found;i++) { |
| if (strcasecmp (command,ext2_commands.names [i])==0) { |
| (*ext2_commands.callback [i]) (command_line); |
| found=1; |
| } |
| } |
| |
| |
| /* 3. If not found, search the general commands */ |
| |
| if (!found) |
| for (i=0;i<=general_commands.last_command && !found;i++) { |
| if (strcasecmp (command,general_commands.names [i])==0) { |
| (*general_commands.callback [i]) (command_line); |
| found=1; |
| } |
| } |
| |
| /* 4. If not found, issue an error message and return */ |
| |
| if (!found) { |
| wprintw (command_win,"Error: Unknown command\n"); |
| refresh_command_win (); |
| } |
| |
| return (0); |
| } |
| |
| |
| /* |
| * |
| * This function copies the next word in source to the variable dest, |
| * ignoring whitespaces. It returns a pointer to the next word in |
| * source. It is used to split the command line into command and arguments. |
| */ |
| char *parse_word (char *source,char *dest) |
| { |
| char ch,*source_ptr,*target_ptr; |
| |
| if (*source==0) { |
| *dest=0; |
| return (source); |
| }; |
| |
| source_ptr=source;target_ptr=dest; |
| do { |
| ch=*source_ptr++; |
| } while (! (ch>' ' && ch<='z') && ch!=0); |
| |
| while (ch>' ' && ch<='z') { |
| *target_ptr++=ch; |
| ch=*source_ptr++; |
| } |
| |
| *target_ptr=0; |
| |
| source_ptr--; |
| do { |
| ch=*source_ptr++; |
| } while (! (ch>' ' && ch<='z') && ch!=0); |
| |
| return (--source_ptr); |
| } |
| |
| /* |
| * text is the partial command entered by the user; We assume that it |
| * is a part of a command - I didn't write code for smarter completion. |
| * |
| * The state variable is an index which tells us how many possible |
| * completions we already returned to readline. |
| * |
| * We return only one possible completion or (char *) NULL if there |
| * are no more completions. This function will be called by readline |
| * over and over until we tell it to stop. |
| * |
| * While scanning for possible completions, we use the same priority |
| * definition which was used in dispatch. |
| */ |
| #if HAVE_READLINE |
| char *complete_command (char *text,int state) |
| { |
| int state_index=-1; |
| int i,len; |
| |
| len=strlen (text); |
| |
| /* Is the command type specific ? */ |
| |
| if (current_type != NULL) |
| for (i=0;i<=current_type->type_commands.last_command;i++) { |
| if (strncmp (current_type->type_commands.names [i],text,len)==0) { |
| state_index++; |
| if (state==state_index) { |
| return (dupstr (current_type->type_commands.names [i])); |
| } |
| } |
| } |
| |
| /* No, pehaps ext2 specific command then ? */ |
| |
| for (i=0;i<=ext2_commands.last_command;i++) { |
| if (strncmp (ext2_commands.names [i],text,len)==0) { |
| state_index++; |
| if (state==state_index) |
| return (dupstr (ext2_commands.names [i])); |
| } |
| } |
| |
| |
| /* Check for a general command */ |
| |
| for (i=0;i<=general_commands.last_command;i++) { |
| if (strncmp (general_commands.names [i],text,len)==0) { |
| state_index++; |
| if (state==state_index) |
| return (dupstr (general_commands.names [i])); |
| } |
| } |
| |
| /* quit is handled differently */ |
| |
| if (strncmp ("quit",text,len)==0) { |
| state_index++; |
| if (state==state_index) |
| return (dupstr ("quit")); |
| } |
| |
| /* No more completions */ |
| |
| return ((char *) NULL); |
| } |
| #endif |
| |
| |
| /* |
| * Nothing special - Just allocates enough space and copy the string. |
| */ |
| char *dupstr (char *src) |
| { |
| char *ptr; |
| |
| ptr=(char *) malloc (strlen (src)+1); |
| strcpy (ptr,src); |
| return (ptr); |
| } |
| |
| #ifdef DEBUG |
| /* |
| * This function reports an internal error. It is almost not used. One |
| * place in which I do check for internal errors is disk.c. |
| * |
| * We just report the error, and try to continue ... |
| */ |
| void internal_error (char *description,char *source_name,char *function_name) |
| { |
| wprintw (command_win,"Internal error - Found by source: %s.c , function: %s\n",source_name,function_name); |
| wprintw (command_win,"\t%s\n",description); |
| wprintw (command_win,"Press enter to (hopefully) continue\n"); |
| refresh_command_win ();getch ();werase (command_win); |
| } |
| |
| #endif |