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