547 lines
13 KiB
C
547 lines
13 KiB
C
|
/*
|
||
|
|
||
|
/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 occurred.
|
||
|
|
||
|
First written on: March 30 1995
|
||
|
|
||
|
Copyright (C) 1995 Gadi Oxman
|
||
|
|
||
|
*/
|
||
|
|
||
|
#include "config.h"
|
||
|
#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 disables 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 receive 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 interpret 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, perhaps 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
|