Path: utzoo!utgpu!jarvis.csri.toronto.edu!mailrus!tut.cis.ohio-state.edu!bloom-beacon!apple!voder!pyramid!prls!philabs!micomvax!ncc!alberta!ubc-cs!ubc-bdcvax!holm From: holm@ubc-bdcvax.UUCP (Terrence W. Holm) Newsgroups: comp.os.minix Subject: de(1) - minix disk editor (part 1 of 3) Message-ID: <407@ubc-bdcvax.UUCP> Date: 26 Jan 89 22:33:03 GMT Lines: 1503 EFTH MINIX report #63 - January 1989 - de(1) Over Christmas I was cleaning up our test Minix system, and I accidently rm'ed a file (uptime.c) that had not yet made it to the production machine. I was slightly upset, but using fgrep(1), od(1) and dd(1) I recovered the three blocks used by the file. I thought, "boy, wouldn't it be nice if I had a program that would display a block from the file system as 1024 characters. The program would allow me to PAGE UP and PAGE DOWN, and search for an ASCII string anywhere on the disk, and then write out any block to a new file." So, I wrote such a program. It allows movement through- out a file system device, displays information in a couple of formats, will write blocks from the device onto another file, and allows rewriting words on the disk (I assume no responsibility for your use of that command!). The command is called de(1) "disk editor". -------------------- I also made a few changes to the Minix file system to aid recovering files. I-node numbers are retained in directory entries now (they get moved to the end). And all the i-node information is not zeroed-out when a file is unlinked. So, after a file is accidently rm'ed, you can find the old i-node, and then manually go to each of the freed blocks and write them to a new file. The movement and write commands are set up for doing this. And I was happy. I told Ed about it (well, actually I eventually tested it on his file system (his better than mine right!)). He wanted to know if the recovery could be automatic.....I thought it could be.....so I added the 'X' command and an "-r" option. So, believe it or not, you can accidently "rm file", and then immediately "de -r file" and everything comes back! -------------------- You can use de(1) without the file system changes, this gives you just the observation and manual recovery commands. The automatic recovery commands can be used if you decide to do the file system changes. This EFTH report includes a "README", the de(1) sources, a "make" file, a "man" page and 3 "cdiff"s to the Minix file system. Terrence W. Holm holm@bdc.ubc.ca ---------------------------------------------------------- echo x - README gres '^X' '' > README << '/' X de - A Minix Disk Editor X X Terrence W. Holm, Jan. 1989 X X XINTRODUCTION X X The de(1) disk editor allows a system administrator to X look at and modify a Minix file system device. Commands X allow movement throughout a file system device, displaying X information in a couple of formats, writing blocks from X the device onto another file, and rewriting words on the X disk. X X A few changes to the Minix file system aid recovering files. X I-node numbers are retained in directory entries now (they X get moved to the end). And all the i-node information is not X zeroed-out when a file is unlinked. So, after a file is X accidently rm(1)'ed, you can find the old i-node, and then X manually (or automatically) go to each of the freed blocks X and write them to a new file. X X XUSES FOR THE DISK EDITOR X X 1) EDUCATION. Students can look at a file system in X a painless manner. For example you don't have to X use od(1) to look at the zone numbers in i-nodes. X X A simple assignment is to change the size of an un-mounted X floppy disk file system from 360 to 300 blocks. (A more X difficult assignment is to explain why this works, even X though fsck(1) and df(1) do not report the correct number X of free blocks. :-) X X 2) ADMINISTRATION. You can visually check inconsistencies X reported by fsck(1) before letting fsck(1) fix them. X You can change any word on the disk, this greatly simplifies X editing file system information. For example, changing the X size of a block special device is actually fun, no more X "blind" writing to your partitions. X X Bit maps can be displayed with 2048 "bits" per screen, X (on the IBM/PC console), see how your zones are allocated! X X 3) RECOVERING LOST FILES. You can search a disk for an ASCII X string, once found, the block can be written out to a file. X X A one line change to fs/path.c allows users to get the i-node X number for a file after it has been removed from a directory. X X Another couple lines changed in the file system keep the X i-node information available until the i-node is reused X (normally this information is zeroed out when an i-node is X released.) This allows a de(1) user to go to a released X i-node, get all the block numbers, go to these blocks and X write them back to a new file. X X The whole recovery process is automated by running "de -r file". X So, IF a file is unlink(2)'ed (eg. "rm file"), AND IF no one X allocates a new i-node or block in the mean-time, THEN you X can recover the file. X X XRECOVERY SECURITY X X Normally Minix hard disk partitions are r/w only by the super-user, X and floppy disks are r/w by anyone. This means that only "root" X can look at hard disk partitions, but others can use de(1) to play X with their floppy disks. X X When recovering files ("de -r file"), a user requires access to X the major file system partitions. This can be done by: X X (a) Give everyone access to the hard disks. DON'T DO THIS, it X defeats all the file system protection we already have. X X (b) Make de(1) set-uid "root". This is the way to go, IF you X are running a Minix system that has NO ACCESS from the X outside. This allows anyone to execute "de -r file", but only X root to use "de /dev/hd3". De(1) does some checking when X retrieving lost blocks, eg. making sure they really are X free blocks and making sure the user owned the i-node. X BUT, file system information has been lost when the file X was unlink(2)'ed, so de(1) can not be 100% sure that a X recovered block really belonged to the user. THIS IS A X SECURITY HOLE. [Since the only access to my machine is from X observable terminals and their associated humans, I run X de(1) as set-uid root.] X X (c) Keep the disks rw-------, and don't set-uid de(1). This X means that only the super-user can recover lost files. X So, if you accidently "rm", you must tell the system X administrator to "su" and recover your file, (be sure to X inform the other users to stop whatever they are doing X until the file is restored). X X XINSTALLATION X X - Install de.1 in /usr/man/cat1. X X - Install the files: Makefile, README, de.h, de.c, de_stdin.c, X de_stdout.c, de_diskio.c and de_recover.c in commands/de. X Add -F and -T. to the Makefile, if necessary. X X - "make" de(1). If a header file is not found, don't worry: X You probably have it somewhere, just link it to what de(1) X is looking for. This program also requires the subroutine X tolower(3), see EFTH MINIX report #50, if you don't have it. X X - Do you really want set-uid root on de? X X - Patch the files fs/path.c, fs/link.c and fs/open.c. If X you don't patch the file system then the recover option X "-r" and associated commands ('x' and 'X') will not work, X but de(1) is still functional and useful. X X - "make" a new fs, using -DRECOVER. Rebuild a boot diskette. X X XUSING DE(1) FOR THE FIRST TIME X X De(1) starts up in "word" mode at block 0 of the specified X device. Hit the PGDN (or space bar) a few times, observing X all the information on the screen. Each PGUP/PGDN moves to X the next 1024 byte block, (de(1) only knows about 1 block per X zone file systems). Note that "word" mode only displays 32 X bytes at a time, so you are only observing the first 32 bytes X in the first few blocks when you skip using PGDN. X X Now go back to block 3, (zone bit map), using "g 3 ENTER". X Change to "map" mode "v m", and then use the down arrow key X to check each 2 Megs in the zone bit map. X X Now change to "block" mode using "v b". And go to some data X block, eg. "g 1000 ENTER". Use PGUP/PGDN to see what data X is in each nearby block. X X Remember 'h' gives you a help page. X X Try some more commands, for example: 'END', 'I', '/'. X (Note: searching through a whole disk under Minix takes a X long time: 30-60 seconds per megabyte, depending on your X machine, drive and controller, [Minix is embarrassingly slow].) X X Don't worry about looking at a mounted device, you must specify X the "-w" option before the 's' command is operational, and X this command is the only one which will try to modify the X contents of the device. X X XMINIX-ST X X Please contact me if you are interesting in attempting a port X to MINIX-ST. / echo x - Makefile gres '^X' '' > Makefile << '/' XCFLAGS = -Di8088 XDEOBJ = de.s de_stdin.s de_stdout.s de_diskio.s de_recover.s X Xde: $(DEOBJ) X cc -i $(DEOBJ) -o de X chmem =20000 de X chmod 4755 de X chown root de X chgrp bin de X X$(DEOBJ): de.h / echo x - de.1 gres '^X' '' > de.1 << '/' XNAME X de(1) - minix disk editor X XSYNOPSIS X de [-w] /dev/device X X de -r lost_file_name X XDESCRIPTION X De(1) allows a system administrator to examine and modify X a Minix file system device. Interactive observation of a X disk partition is initiated by a command line, for example: X X de /dev/hd2 X X Commands are available to move to any address on the disk X and display the disk block contents. This information may X be presented in one of three visual modes: as two-byte words, X as ASCII characters or as a bit map. The disk may be searched X for a string of characters. If the "-w" option is given, X de(1) will open the device for writing and words may be X modified. X X Lost blocks and files can be recovered using a variety of X commands. The "-r" option supports automated recovery of X files removed by unlink(2). X X X POSITIONING X X Disks are divided into blocks (also called "zones") of 1024 X bytes. De(1) keeps a current address on the disk as a X block number and a byte offset within the block. In some X visual modes the offset is rounded off, for example, in X "word" mode the offset must be even. X X There are different types of blocks on a file system device, X including a super block, bit maps, i-nodes and data blocks. X De(1) knows the type of the current block, but will allow X most positioning commands and visual modes to function X anywhere on the disk. X X The 'f' command (or PGDN on the keypad) moves forward to the X next block, similarly 'b' (PGUP) moves backwards one block. X 'F' (END) moves to the last block and 'B' (HOME) moves to the X first block. X X The arrow keys (or 'u', 'd', 'l' and 'r') change the current X address by small increments. The size of the increment X depends on the current display mode, as shown below. The X various sizes suit each display and pointers move on the X screen to follow each press of an arrow key. X X mode up down left right X X word -2 +2 -32 +32 X block -64 +64 -1 +1 X map -256 +256 -4 +4 X X X The 'g' command allows movement to any specified block. X Like all commands that take arguments, a prompt and X subsequent input are written to the bottom line of the X screen. Numerical entry may be decimal, octal or X hexadecimal, for example 234, -1, 070, 0xf3, -X3C. X X While checking an i-node one may want to move to a block X listed as a zone of the file. The 'G' command takes the X contents at the current address in the device as a block X number and indirectly jumps to that block. X X The address may be set to the start of any i-node using X the 'i' command and supplying an i-node number. The 'I' X command maps a given file name into an i-node address. X The file must exist on the current device and this X device must be mounted so that Minix can stat(2) it. X X X THE DISPLAY X X The first line of the display contains the device name, X the name of the current output file (if one is open) and X the current search string. If de(1) is being run with X the "-w" option then the device name is flagged with "(w)". X If a string is too long to fit on the line it is marked X with "...". X X The second line contains the current block number, the X total number of blocks, and the type of the current block. X The types are: boot, super, i-node bit map, zone bit map, X i-nodes and data block. See section 5.6.2 of the text for X an explanation and a diagram. If the current address is X within a data block then the string "in use" is displayed X if the block corresponds to a set bit in the zone bit map. X X The third line shows the offset in the current block. If X the current address is within either the i-node or zone bit X maps then the i-node or block number corresponding to the X current bit is shown. If the current address is within an X i-node then the i-node number and "in use" status is displayed. X If the address is within a bit map or i-node block, but past X the last usable entry, then the string "padding" is shown. X X The rest of the screen is used to display data from the X current block. There are three visual display modes: X "word", "block" and "map". The 'v' command followed by X 'w', 'b' or 'm' sets the current display mode. X X In "word" mode 16 words, of two bytes each, are shown in X either base 2, 8, 10 or 16. The current base is displayed X to the far right of the screen. It can be changed using the X 'o' command followed by either an 'h' (hexadecimal), 'd' X (decimal), 'o' (octal) or 'b' (binary). X X De(1) knows where i-nodes are, and will display the X contents in a readable format, including the "rwx" bits, X the user name and the time field. If the current page X is at the beginning of the super block, or an executable X file or an ar(1) archive, then de(1) will also inform X the user. In all other cases the contents of the 16 X words are shown to the right as equivalent ASCII X characters. X X In "block" mode a whole block of 1024 bytes is displayed X as ASCII characters, 64 columns by 16 lines. Control codes X are shown as highlighted characters. If the high order bit X is set in any of the 1024 bytes then an "MSB" flag is shown X on the far right of the screen, but these bytes are not X individually marked. X X In "map" mode 2048 bits (256 bytes) are displayed from the X top to the bottom (32 bits) and from the left to the right X of the screen. Bit zero of a byte is towards the top of the X screen. This visual mode is generally used to observe X the bit map blocks. The number of set bits displayed is X written on the far right of the screen. X X X SEARCHING X X A search for an ASCII string is initiated by the '/' command. X Control characters not used for other purposes may be X entered in the search string, for example ^J is an end-of- X line character. The search is from the current position to X the end of the current device. X X Once a search string has been defined by a use of '/', the X next search may be initiated with the 'n' command, (a '/' X followed immediately by an ENTER is equivalent to an 'n'). X X Whenever a search is in progress de(1) will append one X '.' to the prompt line for every 500 blocks searched. X X Some of the positioning commands push the current address X and visual mode in a stack before going to a new address. X These commands are B, F, g, G, i, I, n, x and /. The 'p' X (previous) command pops the last address and visual mode X from the stack. This stack is eight entries deep. X X X MODIFYING THE FILE SYSTEM X X The 's' command will prompt for a data word and store it at X the current address on the disk. This is used to change X information that can not be easily changed by any other X means. X X The data word is 16 bits wide, it may be entered in decimal, X octal or hexadecimal. Remember that the "-w" option must X be specified for the 's' command to operate. Be careful X when modifying a mounted file system. X X X RECOVERING FILES X X Any block on the disk may be written to an output file. X This is used to recover blocks marked as free on the X disk. A write command will request a file name the first X time it is used, on subsequent writes the data is appended X to the current output file. X X The name of the current output file is changed using the X 'c' command. This file should be on a different file system, X to avoid overwriting an i-node or block before it is X recovered. X X An ASCII block is usually recovered using the 'w' command. X This reads the block and writes all non-zero bytes to the X output file. Any byte with the most significant bit set X will have it cleared before writing. The 'W' command writes X the current block (1024 bytes) exactly to the output file. X X When a file is deleted using unlink(2) the i-node number X in the directory is zeroed, but before its removal, it is X copied into the end of the file name field. This allows X the i-node of a deleted file to be found by searching X through a directory. The 'x' command asks for the path X name of a lost file, extracts the old i-node number and X changes the current disk address to the start of the X i-node. X X Once an i-node is found, all of the freed blocks may be X recovered by checking the i-node zone fields, using 'G' X to go to a block, writing it back out using 'w', going X back to the i-node with 'p' and advancing to the next X block. This file extraction process is automated by using X the 'X' command, which goes through the i-node, indirect X and double indirect blocks finding all the block pointers X and recovering all the blocks of the file. X X The 'X' command closes the current output file and asks X for the name of a new output file. All of the disk blocks X must be marked as free, if they are not the command stops X and the file must be recovered manually. X X Automatic recovery may be initiated by the "-r" option on X the command line. Also specified is the path name of a X file just removed by unlink(2). De(1) determines which X mounted file system device held the file and opens it for X reading. The lost i-node is found and the file extracted by X automatically performing an 'x' and an 'X' command. X X The recovered file will be written to /tmp. De(1) will X refuse to automatically recover a file on the same file X system as /tmp. The lost file must have belonged to the X user. If automatic recovery will not complete, then manual X recovery may be performed. X X X EXITING THE DISK EDITOR X X The user can terminate a session with de(1) by typing X 'q', ^C, ^D, or the key associated with sigquit. X X The 'm' command invokes the Minix "sh" shell as a sub- X process. X X For help while using de(1) use 'h'. X X X COMMAND SUMMARY X X PGUP b Back one block X PGDN f Forward one block X HOME B Goto first block X END F Goto last block X X UP u Move back 2/64/256 bytes X DOWN d Move forward 2/64/256 bytes X LEFT l Move back 32/1/4 bytes X RIGHT r Move forward 32/1/4 bytes X X g Goto specified block X G Goto block indirectly X i Goto specified i-node X I Filename to i-node X X / Search X n Next occurrence X p Previous address X X h Help X EOF q Quit X m Minix shell X X v Visual mode (w b m) X o Output base (h d o b) X X c Change file name X w Write ASCII block X W Write block exactly X X x Extract lost directory entry X X Extract lost file blocks X X s Store word X X XNOTES X When entering a line in response to a prompt from de(1) X there are a couple of editing characters available. The X previous character may be erased by typing ^H and the X whole line may be erased by typing ^U. ENTER terminates X the input. If DELETE or a non-ASCII character is typed X then the command requesting the input is aborted. X X The commands 'G', 's' and 'X' will only function if X the current visual display mode is "word". The commands X 'i', 'I' and 'x' change the mode to "word" on X completion. The commands 'G' and '/' change the mode X to "block". These restrictions and automatic mode X conversions are intended to aid the user. X X The "map" mode uses special graphic characters, and X only functions if the user is at the console. X X De(1) generates warnings for illegal user input or if X erroneous data is found on the disk, for example a X corrupted magic number. Warnings appear in the middle X of the screen for two seconds, then the current page X is redrawn. Some minor errors, for example, setting X an unknown visual mode, simply ring the bell. Major X errors, for example i/o problems on the file system X device cause an immediate exit from de(1). X X The i-node and zone bit maps are read from the device X when de(1) starts up. These determine whether "in use" X or "not in use" is displayed in the status field at X the top of the screen. The bit maps are not re-read X while using de(1) and will become out-of-date if X observing a mounted file system. X X De(1) requires termcap definitions for "cm" and "cl". X "so" and "se" will also be used if available. The ANSI X strings generated by the keypad arrows are recognized, X as well as any single character codes defined by "ku", X "kd", "kl" and "kr". X X XSEE ALSO X dd(1), df(1), fdisk(1), fsck(1), mkfs(1), od(1), readfs(1), X unlink(2) X X Andrew S. Tanenbaum. "Operating Systems: Design and X Implementation", Section 5.6: "Overview of the Minix X File System". X XAUTHOR X Terrence W. Holm / echo x - de.c gres '^X' '' > de.c << '/' X/****************************************************************/ X/* */ X/* de.c */ X/* */ X/* Main loop of the "Disk editor". */ X/* */ X/****************************************************************/ X/* origination 1989-Jan-15 Terrence W. Holm */ X/****************************************************************/ X X X#include X#include X#include X#include X#include X#include X#include X#include X#include X X#include X#include X#include X X#include "de.h" X Xstatic char copyright[] = { "de (c) Terrence W. Holm 1989" }; X X X X/****************************************************************/ X/* */ X/* main() */ X/* */ X/* Initialize. Handle the "-r" recovery option if */ X/* specified, else enter the main processing loop. */ X/* */ X/****************************************************************/ X X Xvoid main( argc, argv ) X int argc; X char *argv[]; X X { X de_state s; X char *command_name = argv[0]; X int recover = 0; X X X s.device_mode = O_RDONLY; X X X /* Parse arguments */ X X if ( argc == 3 && strcmp( argv[1], "-r" ) == 0 ) X { X recover = 1; X --argc; X ++argv; X } X else if ( argc == 3 && strcmp( argv[1], "-w" ) == 0 ) X { X s.device_mode = O_RDWR; X --argc; X ++argv; X } X X if ( argc != 2 || *argv[1] == '-' ) X { X fprintf( stderr, "Usage: %s [-w] /dev/device\n", command_name ); X fprintf( stderr, " %s -r lost_file_name\n", command_name ); X exit( 1 ); X } X X X /* Set the effective id to the real id. This eliminates */ X /* any increase in privilege done by a set-uid bit on the */ X /* executable file. We want to be "root" for recovering */ X /* files, because we must be able to read the device. */ X /* However, in normal usage, de(1) should not let just */ X /* anyone look at a file system, thus we drop the privilege. */ X /* */ X /* NOTE: There is a security hole when using "-r" with a */ X /* set-uid de(1). Do not use set-uid root if there is any */ X /* way to externally access your Minix system. */ X X if ( ! recover ) X { X setuid( getuid() ); X setgid( getgid() ); X } X X X /* Set terminal characteristics, and ^C interrupt handler */ X X Save_Term(); X X if ( signal( SIGINT, SIG_IGN ) != SIG_IGN ) X { X signal( SIGINT, Sigint ); X signal( SIGQUIT, Sigint ); X } X X Set_Term(); X X if ( ! Init_Termcap() ) X Error( "Requires a termcap entry" ); X X X X /* Get the device file name. If recovering, also open an output file. */ X X if ( recover ) X { X char *dir_name; X char *file_name; X struct stat device_stat; X struct stat tmp_stat; X X /* Split the path name into a directory and a file name. */ X X if ( strlen(argv[1]) > MAX_STRING ) X Error( "Path name too long" ); X X if ( ! Path_Dir_File( argv[1], &dir_name, &file_name ) ) X Error( "Recover aborted" ); X X /* Find the device holding the directory. */ X X if ( (s.device_name = File_Device( dir_name )) == NULL ) X Error( "Recover aborted" ); X X X /* The output file will be in /tmp with the same file name. */ X X strcpy( s.file_name, TMP ); X strcat( s.file_name, "/" ); X strcat( s.file_name, file_name ); X X X /* Make sure /tmp is not on the same device as the file we */ X /* are trying to recover (we don't want to use up the free */ X /* i-node and blocks before we get a chance to recover them). */ X X if ( stat( s.device_name, &device_stat ) == -1 ) X Error( "Can not stat(2) device %s", s.device_name ); X X if ( stat( TMP, &tmp_stat ) == -1 ) X Error( "Can not stat(2) directory %s", TMP ); X X if ( device_stat.st_rdev == tmp_stat.st_dev ) X Error( "Will not recover files on the same device as %s", TMP ); X X if ( access( s.file_name, F_OK ) == 0 ) X Error( "Will not overwrite file %s", s.file_name ); X X X /* Open the output file. */ X X if ( (s.file_f = fopen( s.file_name, "w" )) == NULL ) X Error( "Can not open file %s", s.file_name ); X X /* Don't let anyone else look at the recovered file */ X X chmod( s.file_name, 0700 ); X X /* If running as root then change the owner of the */ X /* restored file. If not running as root then the */ X /* chown(2) will fail. */ X X chown( s.file_name, getuid(), getgid() ); X } X else X { X s.device_name = argv[1]; X s.file_name[ 0 ] = '\0'; X } X X X /* Open the device file. */ X X { X struct stat device_stat; X off_t size; X X if ( stat( s.device_name, &device_stat ) == -1 ) X Error( "Can not find file %s", s.device_name ); X X if ( (device_stat.st_mode & S_IFMT) != S_IFBLK && X (device_stat.st_mode & S_IFMT) != S_IFREG ) X Error( "Can only edit block special or regular files" ); X X X if ( (s.device_d = open( s.device_name, s.device_mode )) == -1 ) X Error( "Can not open %s", s.device_name ); X X if ( (size = lseek( s.device_d, 0L, SEEK_END )) == -1 ) X Error( "Error seeking %s", s.device_name ); X X if ( size % K != 0 ) X Warning( "Device size is not a multiple of 1024" ); X } X X X /* Initialize the rest of the state record */ X X s.mode = WORD; X s.output_base = 10; X s.search_string[ 0 ] = '\0'; X X { X int i; X X for ( i = 0; i < MAX_PREV; ++i ) X { X s.prev_addr[ i ] = 0L; X s.prev_mode[ i ] = WORD; X } X } X X X sync(); X X Read_Super_Block( &s ); X X Read_Bit_Maps( &s ); X X s.address = 0L; X X X X /* Recover mode basically performs an 'x' and an 'X' */ X X if ( recover ) X { X ino_t inode = Find_Deleted_Entry( &s, argv[1] ); X off_t size; X X if ( inode == 0 ) X { X unlink( s.file_name ); X Error( "Recover aborted" ); X } X X s.address = ( (long) s.first_data - s.inode_blocks ) * K X + (long) (inode - 1) * INODE_SIZE; X X Read_Block( &s, s.buffer ); X X X /* Have found the lost i-node, now extract the blocks. */ X X if ( (size = Recover_Blocks( &s )) == -1L ) X { X unlink( s.file_name ); X Error( "Recover aborted" ); X } X X Reset_Term(); X X printf( "Recovered %ld bytes, written to file %s\n", size, s.file_name ); X X exit( 0 ); X } X X X /* Enter the main loop, first time redraw the screen */ X { X int rc = REDRAW; X X X do X { X if ( rc == REDRAW ) X { X Read_Block( &s, s.buffer ); X Draw_Screen( &s ); X s.last_addr = s.address; X Draw_Pointers( &s ); X } X X else if ( rc == REDRAW_POINTERS ) X { X s.offset = s.address & ~ K_MASK; X Draw_Pointers( &s ); X } X X else if ( rc == ERROR ) X { X Erase_Prompt(); X putchar( BELL ); X } X } while ( (rc = Process( &s, Arrow_Esc(Get_Char()) )) != EOF ); X } X X X /* If there is an open output file that was never written to */ X /* then remove its directory entry. This occurs when no 'w' */ X /* or 'W' command occurred between a 'c' command and exiting */ X /* the program. */ X X if ( s.file_name[0] != '\0' && ! s.file_written ) X unlink( s.file_name ); X X X Reset_Term(); /* Restore terminal characteristics */ X X exit( 0 ); X } X X X X X X X/****************************************************************/ X/* */ X/* Process( state, input_char ) */ X/* */ X/* Determine the function requested by the */ X/* input character. Returns OK, REDRAW, */ X/* REDRAW_POINTERS, ERROR or EOF. */ X/* */ X/****************************************************************/ X X Xint Process( s, c ) X de_state *s; X int c; X X { X switch ( c ) X { X case 'b' : /* Back up one block */ X case ESC_PGUP : X X if ( s->address == 0 ) X return( ERROR ); X X s->address = (s->address - K) & K_MASK; X X return( REDRAW ); X X X case 'B' : /* Back up to home */ X case ESC_HOME : X X if ( s->address == 0 ) X return( OK ); X X Push( s ); X X s->address = 0L; X X return( REDRAW ); X X X case 'c' : /* Change file name */ X X { X int rc = Get_Filename( s ); X X return( rc == OK ? REDRAW : rc ); X } X X X case 'd' : /* Down */ X case ESC_DOWN : X X { X s->last_addr = s->address; X X switch ( s->mode ) X { X case WORD : s->address += 2; X X if ( (s->address & PAGE_MASK) == 0 ) X return( REDRAW ); X X return( REDRAW_POINTERS ); X X case BLOCK : s->address += 64; X X if ( (s->last_addr & K_MASK) != X (s->address & K_MASK) ) X return( REDRAW ); X X return( REDRAW_POINTERS ); X X case MAP : s->address += 256; X X return( REDRAW ); X X default : Error( "Internal fault (mode)" ); X } X } X X X case 'f' : /* Forward one block */ X case ' ' : X case ESC_PGDN : X X if ( s->block == s->device_size - 1 ) X return( ERROR ); X X s->address = (s->address + K) & K_MASK; X X return( REDRAW ); X X X case 'F' : /* Forward to end */ X case ESC_END : X X { X off_t last_block = ( (long) s->device_size - 1 ) * K; X X if ( s->address == last_block ) X return( OK ); X X Push( s ); X X s->address = last_block; X X return( REDRAW ); X } X X X case 'g' : /* Goto block */ X X { X unsigned block; X X if ( Get_Count( "Block?", &block ) ) X { X if ( block >= s->zones ) X { X Warning( "Block number too large" ); X return( REDRAW ); X } X X Push( s ); X X s->address = (long) block * K; X X return( REDRAW ); X } X else X return( ERROR ); X } X X X case 'G' : /* Goto block indirect */ X X { X unsigned block = *( (unsigned *) &s->buffer[ s->offset ] ); X X if ( s->mode != WORD ) X { X Warning( "Must be in visual mode \"word\"" ); X return( REDRAW ); X } X X if ( block >= s->zones ) X { X Warning( "Block number too large" ); X return( REDRAW ); X } X X Push( s ); X X s->mode = BLOCK; X s->address = (long) block * K; X X return( REDRAW ); X } X X X case 'h' : /* Help */ X case '?' : X X Draw_Help_Screen( s ); X X Wait_For_Key(); X X return( REDRAW ); X X X case 'i' : /* Goto i-node */ X X { X ino_t inode; X X if ( Get_Count( "I-node?", &inode ) ) X { X if ( inode < 1 || inode > s->inodes ) X { X Warning( "Illegal i-node number" ); X return( REDRAW ); X } X X Push( s ); X X s->mode = WORD; X s->address = ( (long) s->first_data - s->inode_blocks ) * K X + (long) (inode - 1) * INODE_SIZE; X X return( REDRAW ); X } X else X return( ERROR ); X } X X X case 'I' : /* Filename to i-node */ X X { X ino_t inode; X char *filename; X X Draw_Prompt( "File name?" ); X X filename = Get_Line(); X X if ( filename == NULL || filename[0] == '\0' ) X return( ERROR ); X X inode = Find_Inode( s, filename ); X X if ( inode ) X { X Push( s ); X X s->mode = WORD; X s->address = ( (long) s->first_data - s->inode_blocks ) * K X + (long) (inode - 1) * INODE_SIZE; X } X X return( REDRAW ); X } X X X case 'l' : /* Left */ X case ESC_LEFT : X X { X s->last_addr = s->address; X X switch ( s->mode ) X { X case WORD : s->address = s->address - 32; X X return( REDRAW ); X X case BLOCK : s->address -= 1; X X if ( (s->last_addr & K_MASK) != X (s->address & K_MASK) ) X return( REDRAW ); X X return( REDRAW_POINTERS ); X X case MAP : s->address -= 4; X X if ( (s->last_addr & ~ MAP_MASK) != X (s->address & ~ MAP_MASK) ) X return( REDRAW ); X X return( REDRAW_POINTERS ); X X default : Error( "Internal fault (mode)" ); X } X } X X X case 'm' : /* Invoke a Minix shell */ X X Reset_Term(); X X Exec_Shell(); X X Set_Term(); X X return( REDRAW ); X X X case 'n' : /* Search for next */ X X { X off_t addr; X X if ( s->search_string[0] == '\0' ) X { X Warning( "No search string defined" ); X return( REDRAW ); X } X X Draw_Prompt( "Searching..." ); X X if ( (addr = Search( s, s->search_string )) == -1L ) X { X Warning( "Search string not found" ); X X Wait_For_Key(); X X return( REDRAW ); X } X X Push( s ); X s->address = addr; X X return( REDRAW ); X } X X X case 'o' : /* Set output base */ X X Draw_Prompt( "Output base?" ); X X switch ( Get_Char() ) X { X case 'h' : s->output_base = 16; X break; X X case 'd' : s->output_base = 10; X break; X X case 'o' : s->output_base = 8; X break; X X case 'b' : s->output_base = 2; X break; X X default : return( ERROR ); X } X X return( REDRAW ); X X X case 'p' : /* Previous address */ X X { X int i; X X s->address = s->prev_addr[ 0 ]; X s->mode = s->prev_mode[ 0 ]; X X for ( i = 0; i < MAX_PREV - 1; ++i ) X { X s->prev_addr[ i ] = s->prev_addr[ i + 1 ]; X s->prev_mode[ i ] = s->prev_mode[ i + 1 ]; X } X X return( REDRAW ); X } X X X case 'q' : /* Quit */ X case EOF : X case CTRL_D : X X return( EOF ); X X X case 'r' : /* Right */ X case ESC_RIGHT : X X { X s->last_addr = s->address; X X switch ( s->mode ) X { X case WORD : s->address += 32; X X return( REDRAW ); X X case BLOCK : s->address += 1; X X if ( (s->last_addr & K_MASK) != X (s->address & K_MASK) ) X return( REDRAW ); X X return( REDRAW_POINTERS ); X X case MAP : s->address += 4; X X if ( (s->last_addr & ~ MAP_MASK) != X (s->address & ~ MAP_MASK) ) X return( REDRAW ); X X return( REDRAW_POINTERS ); X X default : Error( "Internal fault (mode)" ); X } X } X X X case 's' : /* Store word */ X X { X unsigned word; X X if ( s->mode != WORD ) X { X Warning( "Must be in visual mode \"word\"" ); X return( REDRAW ); X } X X if ( s->device_mode == O_RDONLY ) X { X Warning( "Use -w option to open device for writing" ); X return( REDRAW ); X } X X if ( Get_Count( "Store word?", &word ) ) X { X Write_Word( s, word ); X X return( REDRAW ); X } X else X return( ERROR ); X } X X X case 'u' : /* Up */ X case ESC_UP : X X { X s->last_addr = s->address; X X switch ( s->mode ) X { X case WORD : s->address -= 2; X X if ( (s->last_addr & PAGE_MASK) == 0 ) X return( REDRAW ); X X return( REDRAW_POINTERS ); X X case BLOCK : s->address -= 64; X X if ( (s->last_addr & K_MASK) != X (s->address & K_MASK) ) X return( REDRAW ); X X return( REDRAW_POINTERS ); X X case MAP : s->address -= 256; X X return( REDRAW ); X X default : Error( "Internal fault (mode)" ); X } X } X X X case 'v' : /* Visual mode */ X X Draw_Prompt( "Visual mode?" ); X X switch ( Get_Char() ) X { X case 'w' : s->mode = WORD; X break; X X case 'b' : s->mode = BLOCK; X break; X X case 'm' : { X char *tty = ttyname( 0 ); X X if ( tty == NULL || X strcmp( tty, "/dev/tty0" ) != 0 ) X Warning( "Must be at console" ); X else X s->mode = MAP; X X break; X } X X default : return( ERROR ); X } X X return( REDRAW ); X X X case 'w' : /* Write block w/o ^@ */ X X if ( s->file_name[0] == '\0' ) X { X int rc = Get_Filename( s ); X X if ( rc != OK ) X return( rc ); X } X X /* We have a successfully opened file */ X X /* Eliminate non-ASCII characters */ X { X int i; X char buf[ K ]; X char *from = s->buffer; X char *to = buf; X X for ( i = 0; i < K; ++i, ++from ) X { X if ( *from & 0x80 ) X *to++ = *from & 0x7f; X else if ( *from != 0 ) X *to++ = *from; X } X X if ( fwrite( buf, 1, to - buf, s->file_f ) != to - buf ) X Warning( "Problem writing out buffer" ); X X s->file_written = 1; X X return( REDRAW ); X } X X X case 'W' : /* Write block exactly */ X X if ( s->file_name[0] == '\0' ) X { X int rc = Get_Filename( s ); X X if ( rc != OK ) X return( rc ); X } X X /* We have a successfully opened file */ X X if ( fwrite( s->buffer, 1, K, s->file_f ) != K ) X Warning( "Problem writing out buffer" ); X X s->file_written = 1; X X return( REDRAW ); X X X case 'x' : /* eXtract lost entry */ X X { X ino_t inode; X char *filename; X X Draw_Prompt( "Lost file name?" ); X X filename = Get_Line(); X X if ( filename == NULL || filename[0] == '\0' ) X return( ERROR ); X X inode = Find_Deleted_Entry( s, filename ); X X if ( inode ) X { X Push( s ); X X s->mode = WORD; X s->address = ( (long) s->first_data - s->inode_blocks ) * K X + (long) (inode - 1) * INODE_SIZE; X } X X return( REDRAW ); X } X X X case 'X' : /* eXtract lost blocks */ X X { X int rc; X off_t size; X X if ( s->mode != WORD ) X { X Warning( "Must be in visual mode \"word\"" ); X return( REDRAW ); X } X X X /* Force a new output file name. */ X X if ( (rc = Get_Filename( s )) != OK ) X return( rc ); X X X Draw_Strings( s ); X X Erase_Prompt(); X Draw_Prompt( "Recovering..." ); X X if ( (size = Recover_Blocks( s )) == -1L ) X unlink( s->file_name ); X X /* Force closure of output file. */ X X fclose( s->file_f ); X s->file_name[ 0 ] = '\0'; X X return( REDRAW ); X } X X X case '/' : /* Search */ X case ESC_PLUS : X X { X off_t addr; X char *string; X X Draw_Prompt( "Search string?" ); X X string = Get_Line(); X X if ( string == NULL ) X return( ERROR ); X X if ( string[0] != '\0' ) X { X strcpy( s->search_string, string ); X Draw_Strings( s ); X } X X else if ( s->search_string[0] == '\0' ) X { X Warning( "No search string defined" ); X return( REDRAW ); X } X X Erase_Prompt(); X Draw_Prompt( "Searching..." ); X X if ( (addr = Search( s, s->search_string )) == -1L ) --------------------------------------------------------------