[comp.os.minix] de

holm@ubc-bdcvax.UUCP (Terrence W. Holm) (01/27/89)

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 <sys/types.h>
X#include <sys/dir.h>
X#include <ctype.h>
X#include <fcntl.h>
X#include <signal.h>
X#include <stat.h>
X#include <stdio.h>
X#include <string.h>
X#include <unistd.h>
X
X#include <minix/type.h>
X#include <fs/const.h>
X#include <fs/type.h>
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 )
--------------------------------------------------------------

holm@ubc-bdcvax.UUCP (Terrence W. Holm) (01/27/89)

--------------------------------------------------------------
X		  {
X		  Warning( "Search string not found" );
X
X		  Wait_For_Key();
X
X		  return( REDRAW );
X		  }
X
X		Push( s );
X
X		s->mode = BLOCK;
X		s->address = addr;
X
X		return( REDRAW );
X		}
X
X
X    default:
X		return( ERROR );
X    }
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Push( state )						*/
X/*								*/
X/*		Push current address and mode, used by the	*/
X/*		commands B, F, g, G, i, I, n, x and /.  This	*/
X/*		information is popped by the 'p' command.	*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Push( s )
X  de_state *s;
X
X  {
X  int  i;
X
X  for ( i = MAX_PREV - 1;  i > 0;  --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  s->prev_addr[ 0 ] = s->address;
X  s->prev_mode[ 0 ] = s->mode;
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Get_Filename( state )					*/
X/*								*/
X/*		Read and check a filename.			*/
X/*								*/
X/****************************************************************/
X
X
Xint Get_Filename( s )
X  de_state *s;
X
X  {
X  char *filename;
X  char *name;
X  FILE *f;
X
X  Draw_Prompt( "File name?" );
X
X  filename = Get_Line();
X
X  if ( filename == NULL  ||  filename[0] == '\0' )
X    return( ERROR );
X
X
X  for ( name = filename;  *name != '\0';  ++name )
X    if ( ! isgraph( *name ) )
X      {
X      Warning( "File name contains non-graphic characters" );
X      return( REDRAW );
X      }
X
X
X  if ( access( filename, F_OK ) == 0 )
X    {
X    Warning( "Will not overwrite file %s", filename );
X    return( REDRAW );
X    }
X
X  if ( (f = fopen( filename, "w" )) == NULL )
X    {
X    Warning( "Can not open file %s", filename );
X    return( REDRAW );
X    }
X
X  /*  If there is already an open output file then  */
X  /*  close it. If it was never written to then	    */
X  /*  remove its directory entry.		    */
X
X  if ( s->file_name[0] != '\0' )
X    {
X    if ( ! s->file_written )
X      unlink( s->file_name );
X
X    fclose( s->file_f );
X    }
X
X  strcpy( s->file_name, filename );
X  s->file_f = f;
X  s->file_written = 0;
X
X  return( OK );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Get_Count()						*/
X/*								*/
X/*		Read and check a number. Returns non-zero	*/
X/*		if successful.					*/
X/*								*/
X/****************************************************************/
X
X
Xint Get_Count( units, result )
X  char *units;
X  int  *result;
X
X  {
X  char *number;
X
X  Draw_Prompt( units );
X
X  number = Get_Line();
X
X  if ( number == NULL  ||  number[0] == '\0' )
X    return( 0 );
X
X  return( Str_Int( number, result ) );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Str_Int( string, &result )				*/
X/*								*/
X/*		Convert "string" to an int. Returns non-zero	*/
X/*		if successful. Format: [-][0][x]{0-9}*		*/
X/*								*/
X/****************************************************************/
X
X
Xint Str_Int( str, result )
X  char *str;
X  int  *result;
X
X  {
X  int negative = 0;
X  int base = 10;
X  int total = 0;
X  char c;
X
X  while ( *str == ' ' )
X    ++str;
X
X  if ( *str == '-' )
X    {
X    ++str;
X    negative = 1;
X    }
X
X  if ( *str == '0' )
X    {
X    ++str;
X    base = 8;
X    }
X
X  if ( *str == 'x'  ||  *str == 'X' )
X    {
X    ++str;
X    base = 16;
X    }
X
X  if ( *str == '\0'  &&  base != 8 )
X    return( 0 );
X
X  while ( (c = *str++) != '\0' )
X    {
X    if ( c >= '0'  &&  c <= '7' )
X	total = total * base + c - '0';
X    else if ( isdigit( c )  &&  base >= 10 )
X        total = total * base + c - '0';
X    else if ( isxdigit( c )  &&  base == 16 )
X        total = total * base + tolower( c ) - 'a' + 10;
X    else
X	return( 0 );
X    }
X
X  *result = negative ? -total : total;
X  return( 1 );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	In_Use( bit, map )					*/
X/*								*/
X/*		Is the bit set in the map?			*/
X/*								*/
X/****************************************************************/
X
X
Xint In_Use( bit, map )
X  int bit;
X  char *map;
X
X  {
X  return( map[bit >> 3] & (1 << (bit & 07)) );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Find_Inode( state, filename )				*/
X/*								*/
X/*		Find the i-node for the given file name.	*/
X/*								*/
X/****************************************************************/
X
X
Xino_t Find_Inode( s, filename )
X  de_state *s;
X  char *filename;
X
X  {
X  struct stat device_stat;
X  struct stat file_stat;
X  ino_t inode;
X
X
X  if ( fstat( s->device_d, &device_stat ) == -1 )
X    Error( "Can not fstat(2) file system device" );
X
X  if ( stat( filename, &file_stat ) == -1 )
X    {
X    Warning( "Can not find file %s", filename );
X    return( 0 );
X    }
X
X  if ( device_stat.st_rdev != file_stat.st_dev )
X    {
X    Warning( "File is not on device %s", s->device_name );
X    return( 0 );
X    }
X
X
X  inode = file_stat.st_ino;
X
X  if ( inode < 1  || inode > s->inodes )
X    {
X    Warning( "Illegal i-node number" );
X    return( 0 );
X    }
X
X  return( inode );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Exec_Shell()						*/
X/*								*/
X/*		Fork off a sub-process to exec() the shell.	*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Exec_Shell()
X
X  {
X  int pid = fork();
X
X  if ( pid == -1 )
X    return;
X
X
X  if ( pid == 0 )
X    {
X    /*  The child process  */
X
X    extern char **environ;
X    char *shell  =  getenv( "SHELL" );
X
X    if ( shell == NULL )
X      shell = "/bin/sh";
X
X    execle( shell, shell, (char *) 0, environ );
X
X    perror( shell );
X    exit( 127 );
X    }
X
X
X  /*  The parent process: ignore signals, wait for sub-process	*/
X
X  signal( SIGINT,  SIG_IGN );
X  signal( SIGQUIT, SIG_IGN );
X
X  {
X  int  status;
X  int  w;
X
X  while ( (w=wait(&status)) != pid  &&  w != -1 );
X  }
X
X  signal( SIGINT,  Sigint );
X  signal( SIGQUIT, Sigint );
X
X  return;
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Sigint()						*/
X/*								*/
X/*		Terminate the program on an interrupt (^C)	*/
X/*		or quit (^\) signal.				*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Sigint()
X
X  {
X  Reset_Term();		/*  Restore terminal characteristics	*/
X
X  putchar( '\n' );
X
X  exit( 1 );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Error( message, arg1, arg2 )				*/
X/*								*/
X/*		Print an error message on stderr.		*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Error( message, arg1, arg2 )
X  char *message;
X  char *arg1;
X  char *arg2;
X
X  {
X  Reset_Term();
X
X  fprintf( stderr, "\nde: " );
X  fprintf( stderr, message, arg1, arg2 );
X  fprintf( stderr, "\n" );
X
X  exit( 1 );
X  }
/
echo x - de.h
gres '^X' '' > de.h << '/'
X/****************************************************************/
X/*								*/
X/*	de.h							*/
X/*								*/
X/*		Definitions for the "Disk editor".		*/
X/*								*/
X/****************************************************************/
X/*  origination         1989-Jan-15        Terrence W. Holm	*/
X/****************************************************************/
X
X
X/****************************************************************/
X/*								*/
X/*	de(1)							*/
X/*								*/
X/*  This is the MINIX disk editor. It allows the user to	*/
X/*  observe and modify a file system. It can also be used	*/
X/*  to recover unlink(2)'ed files				*/
X/*								*/
X/*  See the de(1) man page.					*/
X/*								*/
X/****************************************************************/
X
X
X/****************************************************************/
X/*								*/
X/*	de		   Copyright  Terrence W. Holm  1989	*/
X/*								*/
X/* This program was written for users of the Minix operating	*/
X/* system, and in the spirit of other public domain software	*/
X/* written for said system, this source code is made available	*/
X/* at no cost to everyone. I assume no responsibility for	*/
X/* damage to file systems caused by this program.		*/
X/*								*/
X/* This program (one .h, five .c's and a "man" page) may be	*/
X/* copied and/or modified subject to (1) no charge must be	*/
X/* made for distribution, other than for the medium, (2) all	*/
X/* modified sources must be clearly marked as such, (3) all	*/
X/* sources must carry this copyright.				*/
X/*								*/
X/****************************************************************/
X
X
X/****************************************************************/
X/*								*/
X/*	files							*/
X/*								*/
X/*	    de.h		Definitions			*/
X/*	    de.c		The main loop			*/
X/*	    de_stdin.c		Character input routines	*/
X/*	    de_stdout.c		Output routines			*/
X/*	    de_diskio.c		File system read/write		*/
X/*	    de_recover.c	File restoration routines	*/
X/*								*/
X/*	    de.1		"Man" page			*/
X/*	    Makefile		For "make"			*/
X/*	    README		Installation help		*/
X/*								*/
X/*								*/
X/*	fs/path.c was modified to support the 'x' command.	*/
X/*	fs/link.c and fs/open.c were changed for 'X'.		*/
X/*								*/
X/****************************************************************/
X
X
X
X/*  General constants  */
X
X#define   MAX_STRING	60		/*  For all input lines	*/
X#define   MAX_PREV	8		/*  For 'p' command	*/
X#define   SEARCH_BUFFER (4*K)		/*  For '/' and 'n'	*/
X
X
X/*  Files  */
X
X#define   TMP      "/tmp"		/*  For "-r" output	*/
X#define   DEV	   "/dev"		/*  Where devices are	*/
X
X
X/*  a.out header constants  (see a.out.h, if you have it)  */
X
X#ifdef i8088
X#define   A_OUT    0x0301
X#define   SPLIT    0x0420
X#endif
X
X#ifdef ATARI_ST
X#define   A_OUT    0x0301
X#define   SPLIT	   0x0B20
X#endif
X
X
X/*  Each buffer is 1k.  In WORD mode 16 words (32 bytes) can be	*/
X/*  displayed at once. In BLOCK mode 1K bytes can be displayed.	*/
X/*  In MAP mode 2048 bits (256 bytes) are displayed.		*/
X
X#define   K		1024		/*  STD_BLK		*/
X#define   K_MASK	(~(K-1))	/*  Round to K boundary	*/
X#define   K_SHIFT	10		/*  Ie. 1<<10 = K	*/
X#define   PAGE_MASK	0x1f		/*  Word mode: 32 bytes	*/
X#define   PAGE_SHIFT    5		/*  Ie. 1<<5 = 32	*/
X#define   MAP_BITS_PER_BLOCK (8 * K)    /*  1k block, 8192 bits */
X#define   MAP_MASK	0xff		/*  256 bytes/screen	*/
X
X
X
X/*  Terminal i/o codes  */
X
X#define   CTRL_D	'\004'		/*  ASCII ^D		*/
X#define   BELL		'\007'		/*  ASCII bell code     */
X#define   BS		'\010'		/*  ASCII back space	*/
X#define   CTRL_U	'\025'		/*  ASCII ^U		*/
X#define	  ESCAPE  	'\033'		/*  ASCII escape code	*/
X#define   DEL           '\177'		/*  ASCII delete code   */
X
X
X/*  Input escape codes generated by the	Minix console.	*/
X/*  Format: ESC [ X. 					*/
X
X#define   ESC_HOME	('H' + 0x80)
X#define   ESC_UP	('A' + 0x80)
X#define   ESC_PGUP	('V' + 0x80)
X#define   ESC_LEFT	('D' + 0x80)
X#define   ESC_5		('G' + 0x80)
X#define   ESC_RIGHT	('C' + 0x80)
X#define   ESC_END	('Y' + 0x80)
X#define   ESC_DOWN	('B' + 0x80)
X#define   ESC_PGDN	('U' + 0x80)
X#define   ESC_PLUS	('T' + 0x80)
X#define   ESC_MINUS	('S' + 0x80)
X
X
X/*  Graphic box codes - only applicable for console display  */
X/*  in visual mode "map".				     */
X
X#ifdef i8088
X#define   BOX_CLR	' '		/*  Empty box		*/
X#define   BOX_ALL	'\333'		/*  Filled box		*/
X#define   BOX_TOP	'\337'		/*  Filled upper half	*/
X#define   BOX_BOT	'\334'		/*  Filled lower half   */
X#endif
X
X#ifdef ATARI_ST
X/*  Please change these.  */
X#define   BOX_CLR	' '		/*  Empty box		*/
X#define   BOX_ALL	'='		/*  Filled box		*/
X#define   BOX_TOP	'-'		/*  Filled upper half	*/
X#define   BOX_BOT	'_'		/*  Filled lower half   */
X#endif
X
X
X/*  Move positions for the output display.  */
X
X#define   STATUS_COLUMN	 2
X#define   STATUS_LINE    0
X#define   BLOCK_COLUMN	 4
X#define   BLOCK_LINE	 4
X#define   INFO_COLUMN	 30
X#define   INFO_LINE	 BLOCK_LINE
X#define   PROMPT_COLUMN	 0
X#define   PROMPT_LINE	 23
X#define   WARNING_COLUMN 10
X#define   WARNING_LINE   10
X
X
X
X/*  Values returned by Process() and Get_Filename()  */
X
X#define   OK		  0		/*  No update required	*/
X#define   REDRAW	  1		/*  Redraw whole screen	*/
X#define   REDRAW_POINTERS 2		/*  Redraw just ptrs	*/
X#define   ERROR		  3		/*  Beep		*/
X
X
X/*  Visual modes  */
X
X#define   WORD	   1
X#define   BLOCK    2
X#define   MAP	   3
X
X
Xtypedef  struct  de_state		/*  State of disk ed.	*/
X  {
X  /*  Information from super block  */
X
X  unsigned inodes;			/*  Number of i-nodes	*/
X  unsigned zones;			/*  Total # of blocks	*/
X  unsigned inode_maps;			/*  I-node map blocks	*/
X  unsigned zone_maps;			/*  Zone map blocks	*/
X  unsigned inode_blocks;		/*  I-node blocks	*/
X  unsigned first_data;			/*  Total non-data blks	*/
X
X  unsigned inodes_in_map;		/*  Bits in i-node map	*/
X  unsigned zones_in_map;		/*  Bits in zone map	*/
X
X  /*  Information from map blocks  */
X
X  char inode_map[ I_MAP_SLOTS * K ];
X  char zone_map[ ZMAP_SLOTS * K ];
X
X  /*  Information for current block  */
X
X  off_t address;			/*  Current address	*/
X  off_t last_addr;			/*  For erasing ptrs	*/
X  unsigned block;			/*  Current block (1K)	*/
X  unsigned offset;			/*  Offset within block	*/
X
X  char buffer[ K ];
X
X  /*  Display state  */
X
X  int  mode;				/*  WORD, BLOCK or MAP	*/
X  int  output_base;			/*  2, 8, 10, or 16	*/
X
X  /*  Search information  */
X
X  char search_string[ MAX_STRING + 1 ];	/*  For '/' and 'n'	*/
X  off_t prev_addr[ MAX_PREV ];		/*  For 'p' command	*/
X  int   prev_mode[ MAX_PREV ];
X
X  /*  File information  */
X
X  char *device_name;			/*  From command line	*/
X  int   device_d;
X  int   device_mode;			/*  O_RDONLY or O_RDWR	*/
X  unsigned device_size;			/*  Number of blocks	*/
X
X  char  file_name[ MAX_STRING + 1 ];	/*  For 'w' and 'W'	*/
X  FILE *file_f;
X  int   file_written;			/*  Flag if written to	*/
X
X  }  de_state;
X
X
X
X/*  Forward references for external routines  */
X
X/*  libc.a  */
X
Xstruct passwd *getpwuid();
Xstruct group *getgrgid();
Xchar *ctime();
Xchar *getenv();
Xchar *tgetstr();
Xchar *tgoto();
Xchar *ttyname();
XFILE *fopen();
Xoff_t lseek();
X
X
X/*  de.c  */
X
Xvoid  main();
Xint   Process();
X
Xvoid  Push();
Xint   Get_Filename();
Xint   Get_Count();
Xint   Str_Int();
Xint   In_Use();
Xino_t Find_Inode();
Xvoid  Exec_Shell();
Xvoid  Sigint();
Xvoid  Error();
X
X
X/*  de_stdin.c  */
X
Xvoid  Save_Term();
Xvoid  Set_Term();
Xvoid  Reset_Term();
Xint   Get_Char();
Xchar *Get_Line();
Xint   Arrow_Esc();
X
X
X/*  de_stdout.c  */
X
Xint   Init_Termcap();
Xvoid  Goto();
Xvoid  Draw_Help_Screen();
Xvoid  Wait_For_Key();
Xvoid  Draw_Prompt();
Xvoid  Erase_Prompt();
X
Xvoid  Draw_Screen();
Xvoid  Draw_Strings();
Xvoid  Block_Type();
Xvoid  Draw_Words();
Xvoid  Draw_Info();
Xvoid  Draw_Block();
Xvoid  Draw_Map();
X
Xvoid  Draw_Pointers();
Xvoid  Draw_Offset();
Xvoid  Word_Pointers();
Xvoid  Block_Pointers();
Xvoid  Map_Pointers();
X
Xvoid  Print_Number();
Xvoid  Print_Ascii();
Xvoid  Warning();
X
X
X/*  de_diskio.c  */
X
Xvoid  Read_Disk();
Xvoid  Read_Block();
Xvoid  Read_Super_Block();
Xvoid  Read_Bit_Maps();
Xoff_t Search();
Xvoid  Write_Word();
X
X
X/*  de_recover.c  */
X
Xint   Path_Dir_File();
Xchar *File_Device();
Xino_t Find_Deleted_Entry();
Xoff_t Recover_Blocks();
X
X
X#undef    printf			/*  Because fs/const.h	*/
X					/*  defines it.		*/
X
/
echo x - de_diskio.c
gres '^X' '' > de_diskio.c << '/'
X/****************************************************************/
X/*								*/
X/*	de_diskio.c						*/
X/*								*/
X/*		Reading and writing to a file system device.	*/
X/*								*/
X/****************************************************************/
X/*  origination         1989-Jan-15        Terrence W. Holm	*/
X/****************************************************************/
X
X
X#include <sys/types.h>
X#include <stdio.h>
X#include <unistd.h>
X
X#include <minix/const.h>
X#include <minix/type.h>
X#include <fs/const.h>
X#include <fs/type.h>
X#include <fs/super.h>
X
X#include "de.h"
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Read_Disk( state, block_addr, buffer )			*/
X/*								*/
X/*		Reads a 1k block at "block_addr" into "buffer".	*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Read_Disk( s, block_addr, buffer )
X  de_state *s;
X  off_t  block_addr;
X  char  *buffer;
X
X  {
X  if ( lseek( s->device_d, block_addr, SEEK_SET ) == -1 )
X    Error( "Error seeking %s", s->device_name );
X
X  if ( read( s->device_d, buffer, K ) != K )
X    Error( "Error reading %s", s->device_name );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Read_Block( state, buffer )				*/
X/*								*/
X/*		Reads a 1k block from "state->address" into	*/
X/*		"buffer". Checks "address", and updates		*/
X/*		"block" and "offset".				*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Read_Block( s, buffer )
X  de_state *s;
X  char *buffer;
X
X  {
X  off_t end_addr = (long) s->device_size * K - 1;
X  off_t block_addr;
X
X  if ( s->address < 0 )
X    s->address = 0L;
X
X  if ( s->address > end_addr )
X    s->address = end_addr;
X
X  /*  The address must be rounded off for  */
X  /*  certain visual display modes.        */
X
X  if ( s->mode == WORD )
X    s->address &= ~1L;
X  else if ( s->mode == MAP )
X    s->address &= ~3L;
X
X
X  block_addr = s->address & K_MASK;
X
X  s->block  = (unsigned) (block_addr >> K_SHIFT);
X  s->offset = (unsigned) (s->address - block_addr);
X
X  Read_Disk( s, block_addr, buffer );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Read_Super_Block( state )				*/
X/*								*/
X/*		Read and check the super block.			*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Read_Super_Block( s )
X  de_state *s;
X
X  {
X  struct super_block *super = (struct super_block *) s->buffer;
X
X  Read_Disk( s, (long) 1 * K, s->buffer );
X
X  s->inodes = super->s_ninodes;
X  s->zones  = super->s_nzones;
X
X  s->inode_maps   = (s->inodes + MAP_BITS_PER_BLOCK) / MAP_BITS_PER_BLOCK;
X  s->zone_maps    = (s->zones + MAP_BITS_PER_BLOCK - 1) / MAP_BITS_PER_BLOCK;
X	/*  Note: zone_maps may be too large, but we must calculate  */
X	/*  it this way, because this is the way mkfs(1) does it.    */
X
X  s->inode_blocks = (s->inodes + INODES_PER_BLOCK - 1) / INODES_PER_BLOCK;
X  s->first_data   = 2 + s->inode_maps + s->zone_maps + s->inode_blocks;
X
X  s->inodes_in_map = s->inodes + 1;
X  s->zones_in_map  = s->zones + 1 - s->first_data;
X
X  /*
X  if ( s->zones != s->device_size )
X    Warning( "Zone count does not equal device size" );
X  */
X
X  s->device_size = s->zones;
X
X  if ( s->inode_maps != super->s_imap_blocks )
X    Warning( "Corrupted inode map count in super block" );
X
X  if ( s->zone_maps != super->s_zmap_blocks )
X    Warning( "Corrupted zone map count in super block" );
X
X  if ( s->first_data != super->s_firstdatazone )
X    Warning( "Corrupted first data zone count in super block" );
X
X  if ( super->s_log_zone_size != 0 )
X    Warning( "Can not handle multiple blocks per zone" );
X
X  if ( super->s_magic != SUPER_MAGIC )
X    Warning( "Corrupted magic number in super block" );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Read_Bit_Maps( state )					*/
X/*								*/
X/*		Read in the i-node and zone bit maps from the	*/
X/*		specified file system device.			*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Read_Bit_Maps( s )
X  de_state *s;
X
X  {
X  int i;
X
X  if ( s->inode_maps > I_MAP_SLOTS  ||  s->zone_maps > ZMAP_SLOTS )
X    {
X    Warning( "Super block specifies too many bit map blocks" );
X    return;
X    }
X
X  for ( i = 0;  i < s->inode_maps;  ++i )
X    {
X    Read_Disk( s, (long) (2 + i) * K, &s->inode_map[ i * K ] );
X    }
X
X  for ( i = 0;  i < s->zone_maps;  ++i )
X    {
X    Read_Disk( s, (long) (2 + s->inode_maps + i) * K, &s->zone_map[ i * K ] );
X    }
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Search( state, string )					*/
X/*								*/
X/*		Search from the current address for the ASCII	*/
X/*		"string" on the device.				*/
X/*								*/
X/****************************************************************/
X
X
Xoff_t Search( s, string )
X  de_state *s;
X  char *string;
X
X  {
X  off_t address   = s->address + 1;
X  off_t last_addr = address;
X  char  buffer[ SEARCH_BUFFER ];
X  int   offset;
X  int   tail_length = strlen( string ) - 1;
X  int   count = SEARCH_BUFFER;
X  int   last_offset;
X
X
X  for (  ;  count == SEARCH_BUFFER;  address += SEARCH_BUFFER - tail_length )
X    {
X    if ( lseek( s->device_d, address, SEEK_SET ) == -1 )
X      Error( "Error seeking %s", s->device_name );
X
X    if ( (count = read( s->device_d, buffer, SEARCH_BUFFER)) == -1 )
X      Error( "Error reading %s", s->device_name );
X
X
X    if ( address - last_addr >= 500L * K )
X      {
X      putchar( '.' );
X      fflush( stdout );
X
X      last_addr += 500L * K;
X      }
X
X
X    last_offset = count - tail_length;
X
X    for ( offset = 0;  offset < last_offset;  ++offset )
X      {
X      register char c = buffer[ offset ];
X
X      if ( c == *string )
X	{
X	char *tail_buffer = &buffer[ offset + 1 ];
X	char *tail_string = string + 1;
X
X	do
X	  {
X	  if ( *tail_string == '\0' )
X	    return( address + offset );
X	  }
X          while ( *tail_buffer++ == *tail_string++ );
X        }
X      }  /*  end for ( offset )  */
X    }  /*  end for ( address )  */
X
X  return( -1L );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Write_Word( state, word )				*/
X/*								*/
X/*		Write a word at address.			*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Write_Word( s, word )
X  de_state *s;
X  unsigned word;
X
X  {
X  if ( s->address & 01 )
X    Error( "Internal fault (unaligned address)" );
X
X  if ( lseek( s->device_d, s->address, SEEK_SET ) == -1 )
X    Error( "Error seeking %s", s->device_name );
X
X  if ( write( s->device_d, &word, 2 ) != 2 )
X    Error( "Error writing %s", s->device_name );
X  }
/
echo x - de_recover.c
gres '^X' '' > de_recover.c << '/'
X/****************************************************************/
X/*								*/
X/*	de_recover.c						*/
X/*								*/
X/*		File restoration routines.			*/
X/*								*/
X/****************************************************************/
X/*  origination         1989-Jan-21        Terrence W. Holm	*/
X/****************************************************************/
X
X
X#include <sys/types.h>
X#include <sys/dir.h>
X#include <fcntl.h>
X#include <pwd.h>
X#include <stat.h>
X#include <stdio.h>
X#include <string.h>
X#include <unistd.h>
X
X#include <minix/type.h>
X#include <minix/blocksize.h>
X#include <fs/const.h>
X#include <fs/type.h>
X
X#include "de.h"
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Path_Dir_File( path_name, dir_name, file_name )		*/
X/*								*/
X/*		Split "path_name" into a directory name and	*/
X/*		a file name.					*/
X/*								*/
X/*		Zero is returned on error conditions.		*/
X/*								*/
X/****************************************************************/
X
X
Xint Path_Dir_File( path_name, dir_name, file_name )
X  char  *path_name;
X  char **dir_name;
X  char **file_name;
X
X  {
X  char *p;
X  static char directory[ MAX_STRING + 1 ];
X  static char filename[ MAX_STRING + 1 ];
X
X
X  if ( (p = strrchr( path_name, '/' )) == NULL )
X    {
X    strcpy( directory, "." );
X    strcpy( filename, path_name );
X    }
X  else
X    {
X    *directory = '\0';
X    strncat( directory, path_name, p - path_name );
X    strcpy( filename, p + 1 );
X    }
X
X  if ( *directory == '\0' )
X    strcpy( directory, "/" );
X
X  if ( *filename == '\0' )
X    {
X    Warning( "A file name must follow the directory name" );
X    return( 0 );
X    }
X
X  *dir_name  = directory;
X  *file_name = filename;
X
X  return( 1 );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	File_Device( file_name )				*/
X/*								*/
X/*		Return the name of the file system device	*/
X/*		containing the file "file_name".		*/
X/*								*/
X/*		This is used if the "-r" option was specified.	*/
X/*		In this case we have only been given a file	*/
X/*		name, and must determine which file system	*/
X/*		device to open.					*/
X/*								*/
X/*		NULL is returned on error conditions.		*/
X/*								*/
X/****************************************************************/
X
X
X
Xchar *File_Device( file_name )
X  char *file_name;
X
X  {
X  struct stat file_stat;
X  struct stat device_stat;
X  int dev_d;
X  struct direct entry;
X  static char device_name[ DIRSIZ + 1 ];
X
X
X  if ( access( file_name, R_OK ) != 0 )
X    {
X    Warning( "Can not find %s", file_name );
X    return( NULL );
X    }
X
X
X  if ( stat( file_name, &file_stat ) == -1 )
X    {
X    Warning( "Can not stat(2) %s", file_name );
X    return( NULL );
X    }
X
X
X  /*  Open /dev for reading  */
X
X  if ( (dev_d = open( DEV, O_RDONLY )) == -1 )
X    {
X    Warning( "Can not read %s", DEV );
X    return( NULL );
X    }
X
X
X  while ( read( dev_d, (char *) &entry, sizeof(struct direct) )
X				     == sizeof(struct direct) )
X    {
X    if ( entry.d_ino == 0 )
X      continue;
X
X    strcpy( device_name, DEV );
X    strcat( device_name, "/" );
X    strncat( device_name, entry.d_name, DIRSIZ );
X
X    if ( stat( device_name, &device_stat ) == -1 )
X      continue;
X
X    if ( (device_stat.st_mode & S_IFMT) != S_IFBLK )
X      continue;
X
X    if ( file_stat.st_dev == device_stat.st_rdev )
X      {
X      close( dev_d );
X      return( device_name );
X      }
X    }
X
X  close( dev_d );
X
X  Warning( "The device containing file %s is not in %s", file_name, DEV );
X
X  return( NULL );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Find_Deleted_Entry( state, path_name )			*/
X/*								*/
X/*		Split "path_name" into a directory name and	*/
X/*		a file name. Then search the directory for	*/
X/*		an entry that would match the deleted file	*/
X/*		name. (Deleted entries have a zero i-node	*/
X/*		number, but the original i-node number is 	*/
X/*		placed at the end of the file name.)		*/
X/*								*/
X/*		If successful an i-node number is returned,	*/
X/*		else zero is returned.				*/
X/*								*/
X/****************************************************************/
X
X
Xino_t Find_Deleted_Entry( s, path_name )
X  de_state *s;
X  char *path_name;
X
X  {
X  char *dir_name;
X  char *file_name;
X
X
X  /*  Check if the file exists  */
X
X  if ( access( path_name, F_OK ) == 0 )
X    {
X    Warning( "File has not been deleted" );
X    return( 0 );
X    }
X
X
X  /*  Split the path name into a directory and a file name  */
X
X  if ( ! Path_Dir_File( path_name, &dir_name, &file_name ) )
X    return( 0 );
X
X
X  /*  Check to make sure the user has read permission on  */
X  /*  the directory.					  */
X
X  if ( access( dir_name, R_OK ) != 0 )
X    {
X    Warning( "Can not find %s", dir_name );
X    return( 0 );
X    }
X
X
X  /*  Make sure "dir_name" is really a directory. */
X  {
X  struct stat dir_stat;
X
X  if ( stat( dir_name, &dir_stat ) == -1   ||
X		 (dir_stat.st_mode & S_IFMT) != S_IFDIR )
X    {
X    Warning( "Can not find directory %s", dir_name );
X    return( 0 );
X    }
X  }
X
X
X  /*  Make sure the directory is on the current  */
X  /*  file system device.                        */
X
X  if ( Find_Inode( s, dir_name ) == 0 )
X    return( 0 );
X
X
X  /*  Open the directory and search for the lost file name.  */
X  {
X  int   dir_d;
X  int   count;
X  struct direct entry;
X
X  if ( (dir_d = open( dir_name, O_RDONLY )) == -1 )
X    {
X    Warning( "Can not read directory %s", dir_name );
X    return( 0 );
X    }
X
X  while ( (count = read( dir_d, (char *) &entry, sizeof(struct direct) ))
X					      == sizeof(struct direct) )
X    {
X    if ( entry.d_ino == 0  &&
X	strncmp( file_name, entry.d_name, DIRSIZ - sizeof(ino_t) ) == 0 )
X      {
X      ino_t inode = *( (ino_t *) &entry.d_name[ DIRSIZ - sizeof(ino_t) ] );
X
X      close( dir_d );
X
X      if ( inode < 1  || inode > s->inodes )
X    	{
X    	Warning( "Illegal i-node number" );
X    	return( 0 );
X    	}
X
X      return( inode );
X      }
X    }
X
X  close( dir_d );
X
X  if ( count == 0 )
X    Warning( "Can not find a deleted entry for %s", file_name );
X  else
X    Warning( "Problem reading directory %s", dir_name );
X
X  return( 0 );
X  }
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Recover_Blocks( state )					*/
X/*								*/
X/*		Try to recover all the blocks for the i-node	*/
X/*		currently pointed to by "s->address". The	*/
X/*		i-node and all of the blocks must be marked	*/
X/*		as FREE in the bit maps. The owner of the	*/
X/*		i-node must match the current real user name.	*/
X/*								*/
X/*		On any error -1L is returned, otherwise the	*/
X/*		size of the recovered file is returned.		*/
X/*								*/
X/*		NOTE: Once a user has read access to a device,	*/
X/*		there is a security hole, as we lose the	*/
X/*		normal file system protection. For convenience,	*/
X/*		de(1) is sometimes set-uid root, this allows	*/
X/*		anyone to use the "-r" option. When recovering,	*/
X/*		Recover_Blocks() can only superficially check	*/
X/*		the validity of a request.			*/
X/*								*/
X/****************************************************************/
X
X
Xoff_t Recover_Blocks( s )
X  de_state *s;
X
X  {
X  d_inode *inode = (d_inode *) &s->buffer[ s->offset & ~ PAGE_MASK ];
X  int node = (s->address - (s->first_data - s->inode_blocks) * K) /
X		INODE_SIZE + 1;
X
X
X  if ( s->block < s->first_data - s->inode_blocks  ||
X	    s->block >= s->first_data )
X    {
X    Warning( "Not in an inode block" );
X    return( -1L );
X    }
X
X
X  /*  Is this a valid, but free i-node?  */
X
X  if ( node > s->inodes )
X    {
X    Warning( "Not an inode" );
X    return( -1L );
X    }
X
X  if ( In_Use(node, s->inode_map) )
X    {
X    Warning( "I-node is in use" );
X    return( -1L );
X    }
X
X
X  /*  Only recover files that belonged to the real user.  */
X
X  {
X  uid real_uid = getuid();
X  struct passwd *user = getpwuid( real_uid );
X
X  if ( real_uid != SU_UID  &&  real_uid != inode->i_uid )
X    {
X    Warning( "I-node did not belong to user %s", user ? user->pw_name : "" );
X    return( -1L );
X    }
X  }
X
X
X  /*  Recover all the blocks of the file.  */
X
X  {
X  off_t file_size = inode->i_size;
X  int i;
X
X
X  /*  Up to 7 block pointers are stored in the i-node.  */
X
X  for ( i = 0;  i < NR_DZONE_NUM;  ++i )
X    {
X    if ( file_size == 0 )
X	return( inode->i_size );
X
X    if ( ! Data_Block( s, inode->i_zone[ i ], &file_size ) )
X      return( -1L );
X    }
X
X  if ( file_size == 0 )
X    return( inode->i_size );
X
X
X  /*  An indirect block can contain up to 512 more block pointers.  */
X
X  if ( ! Indirect( s, inode->i_zone[ NR_DZONE_NUM ], &file_size, 0 ) )
X    return( -1L );
X
X  if ( file_size == 0 )
X    return( inode->i_size );
X
X
X  /*  A double indirect block can contain up to 512 indirect blocks.  */
X
X  if ( ! Indirect( s, inode->i_zone[ NR_DZONE_NUM+1 ], &file_size, 1 ) )
X    return( -1L );
X
X  if ( file_size == 0 )
X    return( inode->i_size );
X
X  Error( "Internal fault (file_size != 0)" );
X  }
X  }
X
X
X
X
X
X
X/*  Indirect( state, block, &file_size, double )
X *
X *  Recover all the blocks pointed to by the indirect block
X *  "block",  up to "file_size" bytes. If "double" is true,
X *  then "block" is a double-indirect block pointing to 512
X *  indirect blocks.
X */
X
X
Xint Indirect( s, block, file_size, double )
X  de_state *s;
X  zone_nr   block;
X  off_t    *file_size;
X  int       double;
X
X  {
X  zone_nr indirect[ NR_INDIRECTS ];
X  int  i;
X
X  if ( ! Free_Block( s, block ) )
X    return( 0 );
X
X
X  Read_Disk( s, (long) block << K_SHIFT, indirect );
X
X  for ( i = 0;  i < NR_INDIRECTS;  ++i )
X    {
X    if ( *file_size == 0 )
X	return( 1 );
X
X    if ( double )
X      {
X      if ( ! Indirect( s, indirect[ i ], file_size, 0 ) )
X	return( 0 );
X      }
X    else
X      {
X      if ( ! Data_Block( s, indirect[ i ], file_size ) )
X        return( 0 );
X      }
X    }
X
X  return( 1 );
X  }
X
X
X
X
X
X
X/*  Data_Block( state, block, &file_size )
X *
X *  If "block" is free then write  Min(file_size, k)
X *  bytes from it onto the current output file.
X *  The file size is decremented accordingly.
X */
X
X
Xint Data_Block( s, block, file_size )
X  de_state *s;
X  zone_nr   block;
----------------------------------------------------------

holm@ubc-bdcvax.UUCP (Terrence W. Holm) (01/27/89)

----------------------------------------------------------
X  off_t    *file_size;
X
X  {
X  char buffer[ K ];
X  off_t block_size = *file_size > K ? K : *file_size;
X
X
X  if ( ! Free_Block( s, block ) )
X    return( 0 );
X
X  Read_Disk( s, (long) block << K_SHIFT, buffer );
X
X
X  if ( fwrite( buffer, 1, (int) block_size, s->file_f ) != (int) block_size )
X    {
X    Warning( "Problem writing %s", s->file_name );
X    return( 0 );
X    }
X
X  *file_size -= block_size;
X  return( 1 );
X  }
X
X
X
X
X
X
X/*  Free_Block( state, block )
X *
X *  Make sure "block" is a valid data block number, and it
X *  has not been allocated to another file.
X */
X
X
Xint Free_Block( s, block )
X  de_state *s;
X  zone_nr  block;
X
X  {
X  if ( block < s->first_data  ||  block >= s->zones )
X    {
X    Warning( "Illegal block number" );
X    return( 0 );
X    }
X
X  if ( In_Use( block - s->first_data + 1, s->zone_map ) )
X    {
X    Warning( "Encountered an \"in use\" data block" );
X    return( 0 );
X    }
X
X  return( 1 );
X  }
X
/
echo x - de_stdin.c
gres '^X' '' > de_stdin.c << '/'
X/****************************************************************/
X/*								*/
X/*	de_stdin.c						*/
X/*								*/
X/*		Processing input from the "de" user.		*/
X/*								*/
X/****************************************************************/
X/*  origination         1989-Jan-15        Terrence W. Holm	*/
X/****************************************************************/
X
X
X#include <sys/types.h>
X#include <sgtty.h>
X#include <signal.h>
X#include <stdio.h>
X
X#include <fs/const.h>
X
X#include "de.h"
X
X
X
X/****************************************************************/
X/*								*/
X/*	Save_Term()						*/
X/*								*/
X/*		Save the current terminal characteristics.	*/
X/*								*/
X/*								*/
X/*	Set_Term()						*/
X/*								*/
X/*		Set up the terminal characteristics.		*/
X/*								*/
X/*								*/
X/*	Reset_Term()						*/
X/*								*/
X/*		Restore the terminal characteristics.		*/
X/*								*/
X/****************************************************************/
X
X
Xstatic struct sgttyb saved_mode;
Xstatic struct tchars saved_chars;
X
X
X
Xvoid Save_Term()
X
X  {
X  ioctl( 0, TIOCGETP, &saved_mode  );
X  ioctl( 0, TIOCGETC, &saved_chars );
X  }
X
X
X
X
Xvoid Set_Term()
X
X  {
X  struct sgttyb mode;
X  struct tchars chars;
X
X  mode  = saved_mode;
X  chars = saved_chars;
X
X
X  /*  No tab expansion, no echo, don't map ^M to ^J, cbreak mode  */
X
X  mode.sg_flags = mode.sg_flags & ~XTABS & ~ECHO & ~CRMOD  |  CBREAK;
X
X
X  /*  Change the interrupt character to ^C  */
X
X  chars.t_intrc  = '\003';
X
X  ioctl( 0, TIOCSETP, &mode  );
X  ioctl( 0, TIOCSETC, &chars );
X  }
X
X
X
X
Xvoid Reset_Term()
X
X  {
X  ioctl( 0, TIOCSETP, &saved_mode  );
X  ioctl( 0, TIOCSETC, &saved_chars );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Get_Char()						*/
X/*								*/
X/*		Return the next input character. Escape		*/
X/*		sequences are mapped to special codes.		*/
X/*								*/
X/****************************************************************/
X
X
Xint Get_Char()
X  {
X  int c;
X  static int unget_char = EOF;
X
X
X  /*  Flush the output to the screen before waiting  */
X  /*  for input from the user.			     */
X
X  fflush( stdout );
X
X  if ( unget_char == EOF )
X    {
X    while ( (c = Timed_Get_Char( 60 * 60 )) < EOF )
X      printf( "%c", BELL );
X    }
X  else
X    {
X    c = unget_char;
X    unget_char = EOF;
X    }
X
X  if ( c == EOF )
X    return( EOF );
X
X  if ( c != ESCAPE )
X    return( c );
X
X  if ( (c = Timed_Get_Char( 1 )) <= EOF )
X    return( ESCAPE );
X
X  if ( c != '[' )
X    {
X    unget_char = c;
X    return( ESCAPE );
X    }
X
X  if ( (c = Timed_Get_Char( 1 )) <= EOF )
X    {
X    unget_char = '[';
X    return( ESCAPE );
X    }
X
X  return( c | 0x80 );   /* Flag ESC [ x  */
X  }
X
X
X
X
XTimed_Out()
X  {}
X
X
X
X
Xint Timed_Get_Char( time )
X  int time;
X
X  {
X  char c;
X  int  count;
X
X  signal( SIGALRM, Timed_Out );
X
X  alarm( time );
X  count = read( 0, &c, 1 );
X  alarm( 0 );
X
X  if ( count <= 0 )
X    return( EOF + count );
X
X  return( c & 0x7f );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Get_Line()						*/
X/*								*/
X/*		Read a line from the user. Returns a pointer	*/
X/*		to a local buffer, or NULL if DEL or a non-	*/
X/*		ASCII character was typed. Processes ^H and	*/
X/*		^U. ^M terminates the input.			*/
X/*								*/
X/****************************************************************/
X
X
Xchar *Get_Line()
X
X  {
X  int c;
X  int i;
X  static char line[ MAX_STRING + 1 ];
X
X  for ( i = 0;  i <= MAX_STRING;  ++i )
X    {
X    c = Get_Char();
X
X    if ( c == EOF  ||  c == DEL  ||  (c & 0x80) )
X	return( NULL );
X
X    if ( c == BS )
X	{
X	if ( --i >= 0 )
X	  {
X	  printf( "\b \b" );
X	  --i;
X	  }
X	}
X
X    else if ( c == CTRL_U )
X	{
X	for ( --i;  i >= 0;  --i )
X	  printf( "\b \b" );
X	}
X
X    else if ( c == '\r' )
X	{
X	line[ i ] = '\0';
X	return( line );
X	}
X
X    else if ( i < MAX_STRING )
X	{
X	line[ i ] = c;
X	Print_Ascii( c );
X	}
X
X    else  /*  Line buffer is full, don't add any more to it.  */
X	{
X	putchar( BELL );
X	--i;
X	}
X    }
X
X  Error( "Internal fault (line buffer overflow)" );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Arrow_Esc( char )					*/
X/*								*/
X/*		If the keyboard does not generate Ansi escape	*/
X/*		codes for the arrow keys, but does generate	*/
X/*		single byte control codes, then map these	*/
X/*		codes to the special characters we are using	*/
X/*		to denote the Ansi escape codes.		*/
X/*								*/
X/****************************************************************/
X
X
Xextern  char   Kup;		/* (ku) - Up arrow key		*/
Xextern  char   Kdown;		/* (kd) - Down arrow key	*/
Xextern  char   Kleft;		/* (kl) - Left arrow key	*/
Xextern  char   Kright;		/* (kr) - Right arrow key	*/
X
X
Xint Arrow_Esc( c )
X  int c;
X
X  {
X  if ( c == Kup )
X    return( ESC_UP );
X
X  if ( c == Kdown )
X    return( ESC_DOWN );
X
X  if ( c == Kleft )
X    return( ESC_LEFT );
X
X  if ( c == Kright )
X    return( ESC_RIGHT );
X
X  return( c );
X  }
/
echo x - de_stdout.c
gres '^X' '' > de_stdout.c << '/'
X/****************************************************************/
X/*								*/
X/*	de_stdout.c						*/
X/*								*/
X/*		Displaying information from the "Disk editor".	*/
X/*								*/
X/****************************************************************/
X/*  origination         1989-Jan-15        Terrence W. Holm	*/
X/****************************************************************/
X
X
X#include <sys/types.h>
X#include <ar.h>
X#include <fcntl.h>
X#include <grp.h>
X#include <pwd.h>
X#include <stat.h>
X#include <stdio.h>
X
X#include <minix/type.h>
X#include <fs/const.h>
X#include <fs/type.h>
X
X#include "de.h"
X
X
X/****************************************************************/
X/*   		Code for handling termcap			*/
X/****************************************************************/
X
X
X#define  TC_BUFFER  1024	/* Size of termcap(3) buffer	*/
X#define  TC_STRINGS  200	/* Enough room for cm,cl,so,se	*/
X
X
Xstatic  char  *Tmove;		/* (cm) - Format for tgoto	*/
Xstatic  char  *Tclr_all;	/* (cl) - Clear screen  	*/
Xstatic  char  *Treverse;	/* (so) - Start reverse mode 	*/
Xstatic  char  *Tnormal;		/* (se) - End reverse mode	*/
X
Xchar   Kup    = 0;		/* (ku) - Up arrow key		*/
Xchar   Kdown  = 0;		/* (kd) - Down arrow key	*/
Xchar   Kleft  = 0;		/* (kl) - Left arrow key	*/
Xchar   Kright = 0;		/* (kr) - Right arrow key	*/
X
X
X
X/****************************************************************/
X/*								*/
X/*	Init_Termcap()						*/
X/*								*/
X/*		Initializes the external variables for the	*/
X/*		current terminal.				*/
X/*								*/
X/****************************************************************/
X
X
Xint Init_Termcap()
X
X  {
X  char  *term;
X  char   buffer[ TC_BUFFER ];
X  static char strings[ TC_STRINGS ];
X  char  *s = &strings[0];
X  char  *Kcode;
X
X
X  term = getenv( "TERM" );
X
X  if ( term == NULL )
X    return( 0 );
X
X  if ( tgetent( buffer, term ) != 1 )
X    return( 0 );
X
X
X  if ( (Tmove = tgetstr( "cm", &s )) == NULL )
X    return( 0 );
X
X  if ( (Tclr_all = tgetstr( "cl", &s )) == NULL )
X    return( 0 );
X
X  if ( (Treverse = tgetstr( "so", &s )) == NULL )
X    {
X    Treverse = Tnormal = s;
X    *s = '\0';
X    ++s;
X    }
X  else if ( (Tnormal = tgetstr( "se", &s )) == NULL )
X    return( 0 );
X
X
X  /*  See if there are single character arrow key codes  */
X
X  if ( (Kcode = tgetstr( "ku", &s )) != NULL  &&  strlen( Kcode ) == 1 )
X    Kup = Kcode[0];
X
X  if ( (Kcode = tgetstr( "kd", &s )) != NULL  &&  strlen( Kcode ) == 1 )
X    Kdown = Kcode[0];
X
X  if ( (Kcode = tgetstr( "kl", &s )) != NULL  &&  strlen( Kcode ) == 1 )
X    Kleft = Kcode[0];
X
X  if ( (Kcode = tgetstr( "kr", &s )) != NULL  &&  strlen( Kcode ) == 1 )
X    Kright = Kcode[0];
X
X
X  return( 1 );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Goto( column, line )					*/
X/*								*/
X/*		Use the termcap string to move the cursor.	*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Goto( column, line )
X  int  column;
X  int  line;
X
X  {
X  fputs( tgoto( Tmove, column, line ), stdout );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*   		       Output routines				*/
X/****************************************************************/
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Draw_Help_Screen()					*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Draw_Help_Screen( s )
X  de_state *s;
X
X  {
X  int down;
X  int right;
X
X  switch ( s->mode )
X    {
X    case WORD  :   down = 2;    right = 32;  break;
X    case BLOCK :   down = 64;   right = 1;   break;
X    case MAP   :   down = 256;  right = 4;   break;
X    }
X
X  printf( "%s                             ", Tclr_all );
X  printf( "%sDE  COMMANDS%s\r\n\n\n", Treverse, Tnormal );
X
X
X  printf( "   PGUP   b   Back one block              h   Help\r\n" );
X  printf( "   PGDN   f   Forward one block           q   Quit\r\n" );
X  printf( "   HOME   B   Goto first block            m   Minix shell\r\n" );
X  printf( "   END    F   Goto last block\r\n" );
X  printf( "                                          v   Visual mode (w b m)\r
\n" );
X  printf( "          g   Goto specified block        o   Output base (h d o b)
\r\n" );
X  printf( "          G   Goto block indirectly\r\n" );
X  printf( "          i   Goto i-node                 c   Change file name\r\n"
 );
X  printf( "          I   Filename to i-node          w   Write ASCII block\r\n
" );
X  printf( "                                          W   Write block exactly\r
\n" );
X  printf( "          /   Search\r\n" );
X  printf( "          n   Next occurrence             x   Extract lost entry\r\
n" );
X  printf( "          p   Previous address            X   Extract lost blocks\r
\n" );
X  printf( "                                          s   Store word\r\n" );
X  printf( "   UP     u   Move back %d bytes\r\n", down );
X  printf( "   DOWN   d   Move forward %d bytes\r\n", down );
X  printf( "   LEFT   l   Move back %d byte%s\r\n", right,
X					right == 1 ? "" : "s" );
X  printf( "   RIGHT  r   Move forward %d byte%s\r\n\n\n", right,
X					right == 1 ? "" : "s" );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Wait_For_Key()						*/
X/*								*/
X/*		The user must press a key to continue.		*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Wait_For_Key()
X
X  {
X  Draw_Prompt( "Press a key to continue..." );
X
X  Get_Char();
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Draw_Prompt( string )					*/
X/*								*/
X/*		Write a message in the "prompt" area.		*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Draw_Prompt( string )
X  char  *string;
X
X  {
X  Goto( PROMPT_COLUMN, PROMPT_LINE );
X
X  printf( "%s%s%s ", Treverse, string, Tnormal );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Erase_Prompt()						*/
X/*								*/
X/*		Erase the message in the "prompt" area.		*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Erase_Prompt()
X
X  {
X  Goto( PROMPT_COLUMN, PROMPT_LINE );
X
X  printf( "%77c", ' ' );
X
X  Goto( PROMPT_COLUMN, PROMPT_LINE );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Draw_Screen( state )					*/
X/*								*/
X/*		Redraw everything, except pointers.		*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Draw_Screen( s )
X  de_state *s;
X
X  {
X  fputs( Tclr_all, stdout );
X
X  Draw_Strings( s );
X  Block_Type( s );
X
X  switch ( s->mode )
X    {
X    case WORD :   Draw_Words( s );
X		  Draw_Info( s );
X		  break;
X
X    case BLOCK :  Draw_Block( s->buffer );
X		  break;
X
X    case MAP :	  {
X		  int max_bits = 2 * K;
X
X		  /*  Don't display the bits after the end  */
X		  /*  of the i-node or zone bit maps.	    */
X
X		  if ( s->block == 2 + s->inode_maps - 1 )
X		    max_bits = s->inodes_in_map - 8 * K * (s->inode_maps - 1)
X				 - 8 * (s->offset & ~ MAP_MASK);
X
X		  else if ( s->block == 2 + s->inode_maps + s->zone_maps - 1 )
X		    max_bits = s->zones_in_map - 8 * K * (s->zone_maps - 1)
X				 - 8 * (s->offset & ~ MAP_MASK);
X
X		  if ( max_bits < 0 )
X		      max_bits = 0;
X
X		  Draw_Map( &s->buffer[ s->offset & ~ MAP_MASK ], max_bits );
X		  break;
X		  }
X    }
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Draw_Strings( state )					*/
X/*								*/
X/*		The first status line contains the device name,	*/
X/*		the current write file name (if one is open)	*/
X/*		and the current search string (if one has	*/
X/*		been defined).					*/
X/*								*/
X/*		Long strings are truncated.			*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Draw_Strings( s )
X  de_state *s;
X
X  {
X  int len;
X  int i;
X
X  Goto( STATUS_COLUMN, STATUS_LINE );
X
X  printf( "Device %s= %-14.14s  ",
X	     s->device_mode == O_RDONLY ? "" : "(w) ", s->device_name );
X
X
X  len = strlen( s->file_name );
X
X  if ( len == 0 )
X    printf( "%29s", " " );
X  else if ( len <= 20 )
X    printf( "File = %-20s  ", s->file_name );
X  else
X    printf( "File = ...%17.17s  ", s->file_name + len - 17 );
X
X
X  len = strlen( s->search_string );
X
X  if ( len == 0 )
X    printf( "%20s", " " );
X  else
X    {
X    printf( "Search = " );
X
X    if ( len <= 11 )
X      {
X      for ( i = 0;  i < len;  ++i )
X        Print_Ascii( s->search_string[ i ] );
X
X      for ( ;  i < 11;  ++i )
X	putchar( ' ' );
X      }
X    else
X      {
X      for ( i = 0;  i < 8;  ++i )
X        Print_Ascii( s->search_string[ i ] );
X
X      printf( "..." );
X      }
X    }
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Block_Type( state )					*/
X/*								*/
X/*		Display the current block type.			*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Block_Type( s )
X  de_state *s;
X
X  {
X  Goto( STATUS_COLUMN, STATUS_LINE + 1 );
X
X  printf( "Block  = %5u of %-5u  ", s->block, s->zones );
X
X
X  if ( s->block == BOOT_BLOCK )
X    printf( "Boot block" );
X
X  else if ( s->block == SUPER_BLOCK )
X    printf( "Super block" );
X
X  else if ( s->block < 2 + s->inode_maps )
X    printf( "I-node bit map" );
X
X  else if ( s->block < 2 + s->inode_maps + s->zone_maps )
X    printf( "Zone bit map" );
X
X  else if ( s->block < s->first_data )
X    printf( "I-nodes" );
X
X  else
X    printf( "Data block  (%sin use)",
X	In_Use(s->block - s->first_data + 1, s->zone_map) ? "" : "not " );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Draw_Words( state )					*/
X/*								*/
X/*		Draw a page in word format.			*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Draw_Words( s )
X  de_state *s;
X
X  {
X  int line;
X  int addr = s->offset & ~ PAGE_MASK;
X
X
X  for ( line = 0;  line < 16;  ++line, addr += 2 )
X    {
X    Goto( BLOCK_COLUMN, BLOCK_LINE + line );
X
X    printf( "%5d  ", addr );
X
X    Print_Number( *( (unsigned *) &s->buffer[ addr ] ), s->output_base );
X    }
X
X  Goto( BLOCK_COLUMN + 64, BLOCK_LINE + 6 );
X  printf( "(base %d)", s->output_base );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Draw_Info( state )					*/
X/*								*/
X/*		Add information to a page drawn in word format.	*/
X/*		The routine recognizes the super block, inodes,	*/
X/*		executables and "ar" archives. If the current	*/
X/*		page is not one of these, then ASCII characters	*/
X/*		are printed from the data words.		*/
X/*								*/
X/****************************************************************/
X
X
Xchar *super_block_info[] =  {	"number of inodes",
X				"number of zones",
X				"inode bit map blocks",
X				"zone bit map blocks",
X				"first data zone",
X				"blocks per zone shift",
X				"maximum file size",
X				"",
X				"magic number"  };
X
X
Xchar *inode_info[] =  {	"",
X			"",
X			"",
X			"",
X			"",
X			"",
X			"",
X			"zone 0",
X			"zone 1",
X			"zone 2",
X			"zone 3",
X			"zone 4",
X			"zone 5",
X			"zone 6",
X			"indirect",
X			"double indirect"  };
X
X
X
Xvoid Draw_Info( s )
X  de_state *s;
X
X  {
X  int i;
X  int page = s->offset >> PAGE_SHIFT;
X
X
X  if ( s->block == SUPER_BLOCK  &&  page == 0 )
X      for ( i = 0;  i < 9;  ++i )
X 	{
X	Goto( INFO_COLUMN, INFO_LINE + i );
X	printf( "%s", super_block_info[ i ] );
X	}
X
X  else if ( s->block >= s->first_data - s->inode_blocks  &&
X	    s->block < s->first_data )
X      {
X      d_inode *inode = (d_inode *) &s->buffer[ s->offset & ~ PAGE_MASK ];
X      int special = 0;
X      int m;
X      struct passwd *user = getpwuid( inode->i_uid );
X      struct group  *grp  = getgrgid( inode->i_gid );
X
X      for ( i = 0;  i < 16;  ++i )
X    	{
X    	Goto( INFO_COLUMN, INFO_LINE + i );
X    	printf( "%s", inode_info[ i ] );
X    	}
X
X      Goto( INFO_COLUMN, INFO_LINE  );
X
X      switch( inode->i_mode & S_IFMT )
X    	{
X    	case S_IFDIR :  printf( "directory  " );
X		    	break;
X
X    	case S_IFCHR :  printf( "character  " );
X		    	special = 1;
X		    	break;
X
X    	case S_IFBLK :  printf( "block  " );
X		   	special = 1;
X		    	break;
X
X    	case S_IFREG :  printf( "regular  " );
X		    	break;
X#ifdef S_IFIFO
X    	case S_IFIFO :  printf( "fifo  " );
X		    	break;
X#endif
X    	default      :  printf( "unknown  " );
X    	}
X
X
X      for ( m = 11;  m >= 0;  --m )
X    	putchar( (inode->i_mode & (1<<m)) ? "xwrxwrxwrtgu"[m] : '-' );
X
X      Goto( INFO_COLUMN, INFO_LINE + 1 );
X      printf( "user %s", user ? user->pw_name : "" );
X
X      Goto( INFO_COLUMN, INFO_LINE + 2 );
X      printf( "file size %lu", inode->i_size );
X
X      Goto( INFO_COLUMN, INFO_LINE + 4 );
X      printf( "%s", ctime( &inode->i_modtime ) );
X
X      Goto( INFO_COLUMN, INFO_LINE + 6 );
X      printf( "links %d, group %s", inode->i_nlinks, grp ? grp->gr_name : "" )
;
X
X      if ( special )
X	{
X        Goto( INFO_COLUMN, INFO_LINE + 7 );
X	printf( "major %d, minor %d", major( inode->i_zone[0] ),
X				      minor( inode->i_zone[0] ) );
X	}
X      }
X
X  else  /*  Print ASCII characters for each byte in page  */
X      {
X      char *p = &s->buffer[ s->offset & ~ PAGE_MASK ];
X
X      for ( i = 0;  i < 16;  ++i )
X        {
X        Goto( INFO_COLUMN, INFO_LINE + i );
X        Print_Ascii( *p++ );
X        Print_Ascii( *p++ );
X        }
X
X      if ( s->block >= s->first_data  &&  page == 0 )
X	{
X	int magic  = (s->buffer[1] << 8) | (s->buffer[0] & 0xff);
X	int second = (s->buffer[3] << 8) | (s->buffer[2] & 0xff);
X
X        /*  Is this block the start of an "ar" archive?  */
X
X	if ( magic == ARMAG )
X	  {
X          Goto( INFO_COLUMN, INFO_LINE );
X	  printf( "\"ar\" archive" );
X	  }
X
X	/*  Is this block the start of an executable file?  */
X
X	else if ( magic == A_OUT )
X	  {
X          Goto( INFO_COLUMN, INFO_LINE );
X	  printf( "executable" );
X
X          Goto( INFO_COLUMN, INFO_LINE + 1 );
X
X	  if ( second == SPLIT )
X	    printf( "separate I & D" );
X	  else
X	    printf( "combined I & D" );
X	  }
X	}
X      }
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Draw_Block( block )					*/
X/*								*/
X/*		Redraw a 1k block in character format.		*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Draw_Block( block )
X  char *block;
X
X  {
X  int line;
X  int column;
X  int reverse = 0;
X  int msb_flag = 0;
X
X
X  for ( line = 0;  line < 16;  ++line )
X    {
X    Goto( BLOCK_COLUMN, BLOCK_LINE + line );
X
X    for ( column = 0;  column < 64;  ++column )
X      {
X      char c = *block++;
X
X      if ( c & 0x80 )
X	{
X	msb_flag = 1;
X	c &= 0x7f;
X	}
X
X      if ( c >= ' '  &&  c < DEL )
X	{
X	if ( reverse )
X	  { fputs( Tnormal, stdout ); reverse = 0; }
X
X        putchar( c );
X	}
X      else
X	{
X	if ( ! reverse )
X	  { fputs( Treverse, stdout ); reverse = 1; }
X
X	putchar( c == DEL ? '?' : '@' + c );
X	}
X      }  /*  end for ( column )  */
X    }  /*  end for ( line )  */
X
X  if ( reverse )
X    { fputs( Tnormal, stdout ); reverse = 0; }
X
X  if ( msb_flag )
X    {
X    Goto( BLOCK_COLUMN + 68, BLOCK_LINE + 6 );
X    fputs( "(MSB)", stdout );
X    }
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Draw_Map( block, max_bits )				*/
X/*								*/
X/*		Redraw a block in a bit map format.		*/
X/*		Display min( max_bits, 2048 ) bits.		*/
X/*								*/
X/*		The 256 bytes in "block" are displayed from	*/
X/*		top to bottom and left to right. Bit 0 of	*/
X/*		a byte is towards the top of the screen.	*/
X/*								*/
X/*		Special graphic codes are used to generate	*/
X/*		two "bits" per character position. So a 16	*/
X/*		line by 64 column display is 32 "bits" by	*/
X/*		64 "bits". Or 4 bytes by 64 bytes.		*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Draw_Map( block, max_bits )
X  char *block;
X  int   max_bits;
X
X  {
X  int line;
X  int column;
X  int bit_count = 0;
X
X  for ( line = 0;  line < 16;  ++line )
X    {
X    char *p = &block[ (line & 0xC) >> 2 ];
X    int shift = (line & 0x3) << 1;
X
X    Goto( BLOCK_COLUMN, BLOCK_LINE + line );
X
X    for ( column = 0;  column < 64;  ++column, p += 4 )
X      {
X      char c = (*p >> shift) & 0x3;
X      int current_bit = ((p - block) << 3) + shift;
X
X      /*  Don't display bits past "max_bits"  */
X
X      if ( current_bit >= max_bits )
X	break;
X
X      /*  If "max_bits" occurs in between the two bits  */
X      /*  I am trying to display as one character, then	*/
X      /*  zero off the high-order bit.			*/
X
X      if ( current_bit + 1 == max_bits )
X	c &= 1;
X
X      switch ( c )
X	{
X	case 0 :  putchar( BOX_CLR );
X		  break;
X
X	case 1 :  putchar( BOX_TOP );
X		  ++bit_count;
X		  break;
X
X	case 2 :  putchar( BOX_BOT );
X		  ++bit_count;
X		  break;
X
X	case 3 :  putchar( BOX_ALL );
X		  bit_count += 2;
X		  break;
X	}
X      }  /*  end for ( column )  */
X    }  /*  end for ( line )  */
X
X
X  Goto( BLOCK_COLUMN + 68, BLOCK_LINE + 6 );
X  printf( "(%d)", bit_count );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Draw_Pointers( state )					*/
X/*								*/
X/*		Redraw the pointers and the offset field.	*/
X/*		The rest of the screen stays intact.		*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Draw_Pointers( s )
X  de_state *s;
X
X  {
X  Draw_Offset( s );
X
X  switch ( s->mode )
X    {
X    case WORD :   Word_Pointers( s->last_addr, s->address );
X		  break;
X
X    case BLOCK :  Block_Pointers( s->last_addr, s->address );
X		  break;
X
X    case MAP :	  Map_Pointers( s->last_addr, s->address );
X		  break;
X    }
X
X  Goto( PROMPT_COLUMN, PROMPT_LINE );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Draw_Offset( state )					*/
X/*								*/
X/*		Display the offset in the current buffer	*/
X/*		and the relative position if within a map	*/
X/*		or i-node block.				*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Draw_Offset( s )
X  de_state *s;
X
X  {
X  Goto( STATUS_COLUMN, STATUS_LINE + 2 );
X
X  printf( "Offset = %5d           ", s->offset );
X
X
X  if ( s->block < 2 )
X    return;
X
X  if ( s->block < 2 + s->inode_maps )
X    {
X    long bit = (s->address - 2 * K) * 8;
X
X    if ( bit < s->inodes_in_map )
X	printf( "I-node %ld of %d     ", bit, s->inodes );
X    else
X	printf( "(padding)                " );
X    }
X
X  else if ( s->block < 2 + s->inode_maps + s->zone_maps )
X    {
X    long bit = (s->address - (2 + s->inode_maps) * K) * 8;
X
X    if ( bit < s->zones_in_map )
X	printf( "Block %ld of %u     ", bit + s->first_data - 1, s->zones );
X    else
X	printf( "(padding)                " );
X    }
X
X  else if ( s->block < s->first_data )
X    {
X    int node = (s->address - (2 + s->inode_maps + s->zone_maps) * K) /
X		INODE_SIZE + 1;
X
X    if ( node <= s->inodes )
X	printf( "I-node %d of %d  (%sin use)       ", node, s->inodes,
X	     In_Use(node, s->inode_map) ? "" : "not " );
X    else
X	printf( "(padding)                             " );
X    }
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Word_Pointers( old_addr, new_addr )			*/
X/*								*/
X/*	Block_Pointers( old_addr, new_addr )			*/
X/*								*/
X/*	Map_Pointers( old_addr, new_addr )			*/
X/*								*/
X/*		Redraw the index pointers for a each type	*/
X/*		of display. The pointer at "old_addr" is	*/
X/*		erased and a new pointer is positioned		*/
X/*		for "new_addr". This makes the screen		*/
X/*		update faster and more pleasant for the user.	*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Word_Pointers( old_addr, new_addr )
X  off_t old_addr;
X  off_t new_addr;
X
X  {
X  int from = ( (int) old_addr & PAGE_MASK ) >> 1;
X  int to   = ( (int) new_addr & PAGE_MASK ) >> 1;
X
X  Goto( BLOCK_COLUMN - 2, BLOCK_LINE + from );
X  putchar( ' ' );
X
X  Goto( BLOCK_COLUMN - 2, BLOCK_LINE + to );
X  putchar( '>' );
X  }
X
X
X
X
Xvoid Block_Pointers( old_addr, new_addr )
X  off_t old_addr;
X  off_t new_addr;
X
X  {
X  int from = (int) old_addr & ~K_MASK;
X  int to   = (int) new_addr & ~K_MASK;
X
X  Goto( BLOCK_COLUMN - 2, BLOCK_LINE + from / 64 );
X  putchar( ' ' );
X
X  Goto( BLOCK_COLUMN - 2, BLOCK_LINE + to / 64 );
X  putchar( '>' );
X
X  Goto( BLOCK_COLUMN + from % 64, BLOCK_LINE + 17 );
X  putchar( ' ' );
X
X  Goto( BLOCK_COLUMN + to % 64, BLOCK_LINE + 17 );
X  putchar( '^' );
X  }
X
X
X
X
Xvoid Map_Pointers( old_addr, new_addr )
X  off_t old_addr;
X  off_t new_addr;
X
X  {
X  int from = ( (int) old_addr & MAP_MASK ) >> 2;
X  int to   = ( (int) new_addr & MAP_MASK ) >> 2;
X
X  Goto( BLOCK_COLUMN + from, BLOCK_LINE + 17 );
X  putchar( ' ' );
X
X  Goto( BLOCK_COLUMN + to, BLOCK_LINE + 17 );
X  putchar( '^' );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Print_Number( number, output_base )			*/
X/*								*/
X/*		Output "number" in the output base.		*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Print_Number( number, output_base )
X  int number;
X  int output_base;
X
X  {
X  switch ( output_base )
X    {
X    case 16 :	printf( "%5x", number );
X		break;
X
X    case 10 :	printf( "%7u", number );
X		break;
X
X    case 8 :	printf( "%7o", number );
X		break;
X
X    case 2 :	{
X      		unsigned int mask;
X      		char pad = ' ';
X
X      		for ( mask = 0x8000;  mask > 1;  mask >>= 1 )
X		  putchar( (mask & number) ? (pad = '0', '1') : pad );
X
X      		putchar( (0x01 & number) ? '1' : '0' );
X
X		break;
X      		}
X
X    default :	Error( "Internal fault (output_base)" );
X    }
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Print_Ascii( char )					*/
X/*								*/
X/*		Display a character in reverse mode if it	*/
X/*		is not a normal printable ASCII character.	*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Print_Ascii( c )
X  char c;
X
X  {
X  c &= 0x7f;
X
X  if ( c < ' ' )
X    printf( "%s%c%s", Treverse, '@' + c, Tnormal );
X  else if ( c == DEL )
X    printf( "%s?%s", Treverse, Tnormal );
X  else
X    putchar( c );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Warning( message, arg1, arg2 )				*/
X/*								*/
X/*		Display a message for 2 seconds.		*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Warning( message, arg1, arg2 )
X  char *message;
X  char *arg1;
X  char *arg2;
X
X  {
X  printf( "%c%s", BELL, Tclr_all );
X
X  Goto( WARNING_COLUMN, WARNING_LINE );
X
X  printf( "%s Warning: ", Treverse );
X  printf( message, arg1, arg2 );
X  printf( " %s", Tnormal );
X
X  sleep( 2 );
X  }
/
echo x - link.cdiff
gres '^X' '' > link.cdiff << '/'
X*** link.c.1.3	Wed Jan 18 22:08:09 1989
X--- link.c	Wed Jan 18 22:08:01 1989
X***************
X*** 189,193 ****
X     * be cleared immediately, even though these fields are also cleared by
X     * alloc_inode(). The function wipe_inode() does the dirty work in both ca
ses.
X     */
X!   wipe_inode(rip);
X! }
X--- 189,198 ----
X     * be cleared immediately, even though these fields are also cleared by
X     * alloc_inode(). The function wipe_inode() does the dirty work in both ca
ses.
X     */
X! #ifdef RECOVER
X!   /* Leave zone numbers for de(1) to recover file after an unlink(2).  */
X!   rip->i_dirt = DIRTY;
X! #else
X!   wipe_inode(rip);
X! #endif
X! }
/
echo x - open.cdiff
gres '^X' '' > open.cdiff << '/'
X*** open.c.1.3	Mon Aug  1 19:10:58 1988
X--- open.c	Wed Jan 18 22:01:47 1989
X***************
X*** 56,62 ****
X  	/* File exists already. */
X  	switch (rip->i_mode & I_TYPE) {
X  	    case I_REGULAR:		/* truncate regular file */
X! 		if ( (r = forbidden(rip, W_BIT, 0)) == OK) truncate(rip);
X  		break;
X  
X  	    case I_DIRECTORY:	/* can't truncate directory */
X--- 56,69 ----
X  	/* File exists already. */
X  	switch (rip->i_mode & I_TYPE) {
X  	    case I_REGULAR:		/* truncate regular file */
X! #ifdef RECOVER
X! 		if ( (r = forbidden(rip, W_BIT, 0)) == OK) {
X! 			truncate(rip);
X! 			wipe_inode(rip);
X! 		}
X! #else
X! 		if ( (r = forbidden(rip, W_BIT, 0)) == OK) truncate(rip);
X! #endif
X  		break;
X  
X  	    case I_DIRECTORY:	/* can't truncate directory */
/
echo x - path.cdiff
gres '^X' '' > path.cdiff << '/'
X*** path.c.1.3	Mon Aug  1 19:10:58 1988
X--- path.c	Wed Jan 18 21:58:21 1989
X***************
X*** 266,271 ****
X--- 266,275 ----
X  				&& cmp_string(dp->d_name, string, NAME_SIZE)) {
X  			/* LOOK_UP or DELETE found what it wanted. */
X  			if (flag == DELETE) {
X+ #ifdef RECOVER
X+ 				*( (inode_nr *) &dp->d_name[ NAME_SIZE - 
X+ 					sizeof(inode_nr) ] ) = dp->d_inum;
X+ #endif
X  				dp->d_inum = 0;	/* erase entry */
X  				bp->b_dirt = DIRTY;
X  				ldir_ptr->i_modtime = clock_time();
/
exit 0
----------------------------------------------------------

holm@ubc-bdcvax.UUCP (Terrence W. Holm) (02/03/89)

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 - Makefile
sed '/^X/s///' > Makefile << '/'
XCFLAGS	= -Di8088 -T. -F
XDEOBJ   = de.s de_stdin.s de_stdout.s de_diskio.s de_recover.s
X
Xde:	$(DEOBJ)
X	cc -i -T. $(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 - README
sed '/^X/s///' > 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 - de.1
sed '/^X/s///' > 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. If the
X    string is found between the end of the file system and the
X    actual end of the device, then the current address is set to
X    the end of the file system.
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    All bytes will have their most significant bit cleared before
X    being written to the output file. Bytes containing '\0' or
X    '\177' are not copied. The 'W' command writes the current
X    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    When extracting lost blocks de(1) will maintain "holes" in
X    the file. Thus, a recovered sparse file does not allocate
X    unused blocks and will keep its efficient storage scheme.
X    This property of the 'X' command may be used to move a sparse
X    file from one device to another.
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
---------------------------------------------------------------

holm@ubc-bdcvax.UUCP (Terrence W. Holm) (02/03/89)

---------------------------------------------------------------
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
sed '/^X/s///' > 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 <sys/types.h>
X#include <sys/dir.h>
X#include <ctype.h>
X#include <fcntl.h>
X#include <signal.h>
X#include <stat.h>
X#include <stdio.h>
X#include <string.h>
X#include <unistd.h>
X
X#include <minix/type.h>
X#include <fs/const.h>
X#include <fs/type.h>
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 ASCII block	*/
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		  *to = *from & 0x7f;
X
X		  if ( *to != '\0'  &&  *to != '\177' )
X		    ++to;
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 );
---------------------------------------------------------------

holm@ubc-bdcvax.UUCP (Terrence W. Holm) (02/03/89)

---------------------------------------------------------------
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 )
X		  {
X		  Warning( "Search string not found" );
X
X		  Wait_For_Key();
X
X		  return( REDRAW );
X		  }
X
X		Push( s );
X
X		s->mode = BLOCK;
X		s->address = addr;
X
X		return( REDRAW );
X		}
X
X
X    default:
X		return( ERROR );
X    }
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Push( state )						*/
X/*								*/
X/*		Push current address and mode, used by the	*/
X/*		commands B, F, g, G, i, I, n, x and /.  This	*/
X/*		information is popped by the 'p' command.	*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Push( s )
X  de_state *s;
X
X  {
X  int  i;
X
X  for ( i = MAX_PREV - 1;  i > 0;  --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  s->prev_addr[ 0 ] = s->address;
X  s->prev_mode[ 0 ] = s->mode;
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Get_Filename( state )					*/
X/*								*/
X/*		Read and check a filename.			*/
X/*								*/
X/****************************************************************/
X
X
Xint Get_Filename( s )
X  de_state *s;
X
X  {
X  char *filename;
X  char *name;
X  FILE *f;
X
X  Draw_Prompt( "File name?" );
X
X  filename = Get_Line();
X
X  if ( filename == NULL  ||  filename[0] == '\0' )
X    return( ERROR );
X
X
X  for ( name = filename;  *name != '\0';  ++name )
X    if ( ! isgraph( *name ) )
X      {
X      Warning( "File name contains non-graphic characters" );
X      return( REDRAW );
X      }
X
X
X  if ( access( filename, F_OK ) == 0 )
X    {
X    Warning( "Will not overwrite file %s", filename );
X    return( REDRAW );
X    }
X
X  if ( (f = fopen( filename, "w" )) == NULL )
X    {
X    Warning( "Can not open file %s", filename );
X    return( REDRAW );
X    }
X
X  /*  If there is already an open output file then  */
X  /*  close it. If it was never written to then	    */
X  /*  remove its directory entry.		    */
X
X  if ( s->file_name[0] != '\0' )
X    {
X    if ( ! s->file_written )
X      unlink( s->file_name );
X
X    fclose( s->file_f );
X    }
X
X  strcpy( s->file_name, filename );
X  s->file_f = f;
X  s->file_written = 0;
X
X  return( OK );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Get_Count()						*/
X/*								*/
X/*		Read and check a number. Returns non-zero	*/
X/*		if successful.					*/
X/*								*/
X/****************************************************************/
X
X
Xint Get_Count( units, result )
X  char *units;
X  int  *result;
X
X  {
X  char *number;
X
X  Draw_Prompt( units );
X
X  number = Get_Line();
X
X  if ( number == NULL  ||  number[0] == '\0' )
X    return( 0 );
X
X  return( Str_Int( number, result ) );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Str_Int( string, &result )				*/
X/*								*/
X/*		Convert "string" to an int. Returns non-zero	*/
X/*		if successful. Format: [-][0][x]{0-9}*		*/
X/*								*/
X/****************************************************************/
X
X
Xint Str_Int( str, result )
X  char *str;
X  int  *result;
X
X  {
X  int negative = 0;
X  int base = 10;
X  int total = 0;
X  char c;
X
X  while ( *str == ' ' )
X    ++str;
X
X  if ( *str == '-' )
X    {
X    ++str;
X    negative = 1;
X    }
X
X  if ( *str == '0' )
X    {
X    ++str;
X    base = 8;
X    }
X
X  if ( *str == 'x'  ||  *str == 'X' )
X    {
X    ++str;
X    base = 16;
X    }
X
X  if ( *str == '\0'  &&  base != 8 )
X    return( 0 );
X
X  while ( (c = *str++) != '\0' )
X    {
X    if ( c >= '0'  &&  c <= '7' )
X	total = total * base + c - '0';
X    else if ( isdigit( c )  &&  base >= 10 )
X        total = total * base + c - '0';
X    else if ( isxdigit( c )  &&  base == 16 )
X        total = total * base + tolower( c ) - 'a' + 10;
X    else
X	return( 0 );
X    }
X
X  *result = negative ? -total : total;
X  return( 1 );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	In_Use( bit, map )					*/
X/*								*/
X/*		Is the bit set in the map?			*/
X/*								*/
X/****************************************************************/
X
X
Xint In_Use( bit, map )
X  int bit;
X  char *map;
X
X  {
X  return( map[bit >> 3] & (1 << (bit & 07)) );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Find_Inode( state, filename )				*/
X/*								*/
X/*		Find the i-node for the given file name.	*/
X/*								*/
X/****************************************************************/
X
X
Xino_t Find_Inode( s, filename )
X  de_state *s;
X  char *filename;
X
X  {
X  struct stat device_stat;
X  struct stat file_stat;
X  ino_t inode;
X
X
X  if ( fstat( s->device_d, &device_stat ) == -1 )
X    Error( "Can not fstat(2) file system device" );
X
X  if ( stat( filename, &file_stat ) == -1 )
X    {
X    Warning( "Can not find file %s", filename );
X    return( 0 );
X    }
X
X  if ( device_stat.st_rdev != file_stat.st_dev )
X    {
X    Warning( "File is not on device %s", s->device_name );
X    return( 0 );
X    }
X
X
X  inode = file_stat.st_ino;
X
X  if ( inode < 1  || inode > s->inodes )
X    {
X    Warning( "Illegal i-node number" );
X    return( 0 );
X    }
X
X  return( inode );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Exec_Shell()						*/
X/*								*/
X/*		Fork off a sub-process to exec() the shell.	*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Exec_Shell()
X
X  {
X  int pid = fork();
X
X  if ( pid == -1 )
X    return;
X
X
X  if ( pid == 0 )
X    {
X    /*  The child process  */
X
X    extern char **environ;
X    char *shell  =  getenv( "SHELL" );
X
X    if ( shell == NULL )
X      shell = "/bin/sh";
X
X    execle( shell, shell, (char *) 0, environ );
X
X    perror( shell );
X    exit( 127 );
X    }
X
X
X  /*  The parent process: ignore signals, wait for sub-process	*/
X
X  signal( SIGINT,  SIG_IGN );
X  signal( SIGQUIT, SIG_IGN );
X
X  {
X  int  status;
X  int  w;
X
X  while ( (w=wait(&status)) != pid  &&  w != -1 );
X  }
X
X  signal( SIGINT,  Sigint );
X  signal( SIGQUIT, Sigint );
X
X  return;
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Sigint()						*/
X/*								*/
X/*		Terminate the program on an interrupt (^C)	*/
X/*		or quit (^\) signal.				*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Sigint()
X
X  {
X  Reset_Term();		/*  Restore terminal characteristics	*/
X
X  putchar( '\n' );
X
X  exit( 1 );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Error( message, arg1, arg2 )				*/
X/*								*/
X/*		Print an error message on stderr.		*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Error( message, arg1, arg2 )
X  char *message;
X  char *arg1;
X  char *arg2;
X
X  {
X  Reset_Term();
X
X  fprintf( stderr, "\nde: " );
X  fprintf( stderr, message, arg1, arg2 );
X  fprintf( stderr, "\n" );
X
X  exit( 1 );
X  }
/
echo x - de.h
sed '/^X/s///' > de.h << '/'
X/****************************************************************/
X/*								*/
X/*	de.h							*/
X/*								*/
X/*		Definitions for the "Disk editor".		*/
X/*								*/
X/****************************************************************/
X/*  origination         1989-Jan-15        Terrence W. Holm	*/
X/****************************************************************/
X
X
X/****************************************************************/
X/*								*/
X/*	de(1)							*/
X/*								*/
X/*  This is the MINIX disk editor. It allows the user to	*/
X/*  observe and modify a file system. It can also be used	*/
X/*  to recover unlink(2)'ed files				*/
X/*								*/
X/*  See the de(1) man page.					*/
X/*								*/
X/****************************************************************/
X
X
X/****************************************************************/
X/*								*/
X/*	de		   Copyright  Terrence W. Holm  1989	*/
X/*								*/
X/* This program was written for users of the Minix operating	*/
X/* system, and in the spirit of other public domain software	*/
X/* written for said system, this source code is made available	*/
X/* at no cost to everyone. I assume no responsibility for	*/
X/* damage to file systems caused by this program.		*/
X/*								*/
X/* This program (one .h, five .c's and a "man" page) may be	*/
X/* copied and/or modified subject to (1) no charge must be	*/
X/* made for distribution, other than for the medium, (2) all	*/
X/* modified sources must be clearly marked as such, (3) all	*/
X/* sources must carry this copyright.				*/
X/*								*/
X/****************************************************************/
X
X
X/****************************************************************/
X/*								*/
X/*	files							*/
X/*								*/
X/*	    de.h		Definitions			*/
X/*	    de.c		The main loop			*/
X/*	    de_stdin.c		Character input routines	*/
X/*	    de_stdout.c		Output routines			*/
X/*	    de_diskio.c		File system read/write		*/
X/*	    de_recover.c	File restoration routines	*/
X/*								*/
X/*	    de.1		"Man" page			*/
X/*	    Makefile		For "make"			*/
X/*	    README		Installation help		*/
X/*								*/
X/*								*/
X/*	fs/path.c was modified to support the 'x' command.	*/
X/*	fs/link.c and fs/open.c were changed for 'X'.		*/
X/*								*/
X/****************************************************************/
X
X
X
X/*  General constants  */
X
X#define   MAX_STRING	60		/*  For all input lines	*/
X#define   MAX_PREV	8		/*  For 'p' command	*/
X#define   SEARCH_BUFFER (4*K)		/*  For '/' and 'n'	*/
X
X
X/*  Files  */
X
X#define   TMP      "/tmp"		/*  For "-r" output	*/
X#define   DEV	   "/dev"		/*  Where devices are	*/
X
X
X/*  a.out header constants  (see a.out.h, if you have it)  */
X
X#ifdef i8088
X#define   A_OUT    0x0301
X#define   SPLIT    0x0420
X#endif
X
X#ifdef ATARI_ST
X#define   A_OUT    0x0301
X#define   SPLIT	   0x0B20
X#endif
X
X
X/*  Each buffer is 1k.  In WORD mode 16 words (32 bytes) can be	*/
X/*  displayed at once. In BLOCK mode 1K bytes can be displayed.	*/
X/*  In MAP mode 2048 bits (256 bytes) are displayed.		*/
X
X#define   K		1024		/*  STD_BLK		*/
X#define   K_MASK	(~(K-1))	/*  Round to K boundary	*/
X#define   K_SHIFT	10		/*  Ie. 1<<10 = K	*/
X#define   PAGE_MASK	0x1f		/*  Word mode: 32 bytes	*/
X#define   PAGE_SHIFT    5		/*  Ie. 1<<5 = 32	*/
X#define   MAP_BITS_PER_BLOCK (8 * K)    /*  1k block, 8192 bits */
X#define   MAP_MASK	0xff		/*  256 bytes/screen	*/
X
X
X
X/*  Terminal i/o codes  */
X
X#define   CTRL_D	'\004'		/*  ASCII ^D		*/
X#define   BELL		'\007'		/*  ASCII bell code     */
X#define   BS		'\010'		/*  ASCII back space	*/
X#define   CTRL_U	'\025'		/*  ASCII ^U		*/
X#define	  ESCAPE  	'\033'		/*  ASCII escape code	*/
X#define   DEL           '\177'		/*  ASCII delete code   */
X
X
X/*  Input escape codes generated by the	Minix console.	*/
X/*  Format: ESC [ X. 					*/
X
X#define   ESC_HOME	('H' + 0x80)
X#define   ESC_UP	('A' + 0x80)
X#define   ESC_PGUP	('V' + 0x80)
X#define   ESC_LEFT	('D' + 0x80)
X#define   ESC_5		('G' + 0x80)
X#define   ESC_RIGHT	('C' + 0x80)
X#define   ESC_END	('Y' + 0x80)
X#define   ESC_DOWN	('B' + 0x80)
X#define   ESC_PGDN	('U' + 0x80)
X#define   ESC_PLUS	('T' + 0x80)
X#define   ESC_MINUS	('S' + 0x80)
X
X
X/*  Graphic box codes - only applicable for console display  */
X/*  in visual mode "map".				     */
X
X#ifdef i8088
X#define   BOX_CLR	' '		/*  Empty box		*/
X#define   BOX_ALL	'\333'		/*  Filled box		*/
X#define   BOX_TOP	'\337'		/*  Filled upper half	*/
X#define   BOX_BOT	'\334'		/*  Filled lower half   */
X#endif
X
X#ifdef ATARI_ST
X/*  Please change these.  */
X#define   BOX_CLR	' '		/*  Empty box		*/
X#define   BOX_ALL	'='		/*  Filled box		*/
X#define   BOX_TOP	'-'		/*  Filled upper half	*/
X#define   BOX_BOT	'_'		/*  Filled lower half   */
X#endif
X
X
X/*  Move positions for the output display.  */
X
X#define   STATUS_COLUMN	 2
X#define   STATUS_LINE    0
X#define   BLOCK_COLUMN	 4
X#define   BLOCK_LINE	 4
X#define   INFO_COLUMN	 30
X#define   INFO_LINE	 BLOCK_LINE
X#define   PROMPT_COLUMN	 0
X#define   PROMPT_LINE	 23
X#define   WARNING_COLUMN 10
X#define   WARNING_LINE   10
X
X
X
X/*  Values returned by Process() and Get_Filename()  */
X
X#define   OK		  0		/*  No update required	*/
X#define   REDRAW	  1		/*  Redraw whole screen	*/
X#define   REDRAW_POINTERS 2		/*  Redraw just ptrs	*/
X#define   ERROR		  3		/*  Beep		*/
X
X
X/*  Visual modes  */
X
X#define   WORD	   1
X#define   BLOCK    2
X#define   MAP	   3
X
X
Xtypedef  struct  de_state		/*  State of disk ed.	*/
X  {
X  /*  Information from super block  */
X
X  unsigned inodes;			/*  Number of i-nodes	*/
X  unsigned zones;			/*  Total # of blocks	*/
X  unsigned inode_maps;			/*  I-node map blocks	*/
X  unsigned zone_maps;			/*  Zone map blocks	*/
X  unsigned inode_blocks;		/*  I-node blocks	*/
X  unsigned first_data;			/*  Total non-data blks	*/
X
X  unsigned inodes_in_map;		/*  Bits in i-node map	*/
X  unsigned zones_in_map;		/*  Bits in zone map	*/
X
X  /*  Information from map blocks  */
X
X  char inode_map[ I_MAP_SLOTS * K ];
X  char zone_map[ ZMAP_SLOTS * K ];
X
X  /*  Information for current block  */
X
X  off_t address;			/*  Current address	*/
X  off_t last_addr;			/*  For erasing ptrs	*/
X  unsigned block;			/*  Current block (1K)	*/
X  unsigned offset;			/*  Offset within block	*/
X
X  char buffer[ K ];
X
X  /*  Display state  */
X
X  int  mode;				/*  WORD, BLOCK or MAP	*/
X  int  output_base;			/*  2, 8, 10, or 16	*/
X
X  /*  Search information  */
X
X  char search_string[ MAX_STRING + 1 ];	/*  For '/' and 'n'	*/
X  off_t prev_addr[ MAX_PREV ];		/*  For 'p' command	*/
X  int   prev_mode[ MAX_PREV ];
X
X  /*  File information  */
X
X  char *device_name;			/*  From command line	*/
X  int   device_d;
X  int   device_mode;			/*  O_RDONLY or O_RDWR	*/
X  unsigned device_size;			/*  Number of blocks	*/
X
X  char  file_name[ MAX_STRING + 1 ];	/*  For 'w' and 'W'	*/
X  FILE *file_f;
X  int   file_written;			/*  Flag if written to	*/
X
X  }  de_state;
X
X
X
X/*  Forward references for external routines  */
X
X/*  libc.a  */
X
Xstruct passwd *getpwuid();
Xstruct group *getgrgid();
Xchar *ctime();
Xchar *getenv();
Xchar *tgetstr();
Xchar *tgoto();
Xchar *ttyname();
XFILE *fopen();
Xoff_t lseek();
X
X
X/*  de.c  */
X
Xvoid  main();
Xint   Process();
X
Xvoid  Push();
Xint   Get_Filename();
Xint   Get_Count();
Xint   Str_Int();
Xint   In_Use();
Xino_t Find_Inode();
Xvoid  Exec_Shell();
Xvoid  Sigint();
Xvoid  Error();
X
X
X/*  de_stdin.c  */
X
Xvoid  Save_Term();
Xvoid  Set_Term();
Xvoid  Reset_Term();
Xint   Get_Char();
Xchar *Get_Line();
Xint   Arrow_Esc();
X
X
X/*  de_stdout.c  */
X
Xint   Init_Termcap();
Xvoid  Goto();
Xvoid  Draw_Help_Screen();
Xvoid  Wait_For_Key();
Xvoid  Draw_Prompt();
Xvoid  Erase_Prompt();
X
Xvoid  Draw_Screen();
Xvoid  Draw_Strings();
Xvoid  Block_Type();
Xvoid  Draw_Words();
Xvoid  Draw_Info();
Xvoid  Draw_Block();
Xvoid  Draw_Map();
X
Xvoid  Draw_Pointers();
Xvoid  Draw_Offset();
Xvoid  Word_Pointers();
Xvoid  Block_Pointers();
Xvoid  Map_Pointers();
X
Xvoid  Print_Number();
Xvoid  Print_Ascii();
Xvoid  Warning();
X
X
X/*  de_diskio.c  */
X
Xvoid  Read_Disk();
Xvoid  Read_Block();
Xvoid  Read_Super_Block();
Xvoid  Read_Bit_Maps();
Xoff_t Search();
Xvoid  Write_Word();
X
X
X/*  de_recover.c  */
X
Xint   Path_Dir_File();
Xchar *File_Device();
Xino_t Find_Deleted_Entry();
Xoff_t Recover_Blocks();
X
X
X#undef    printf			/*  Because fs/const.h	*/
X					/*  defines it.		*/
X
/
echo x - de_diskio.c
sed '/^X/s///' > de_diskio.c << '/'
X/****************************************************************/
X/*								*/
X/*	de_diskio.c						*/
X/*								*/
X/*		Reading and writing to a file system device.	*/
X/*								*/
X/****************************************************************/
X/*  origination         1989-Jan-15        Terrence W. Holm	*/
X/****************************************************************/
X
X
X#include <sys/types.h>
X#include <stdio.h>
X#include <unistd.h>
X
X#include <minix/const.h>
X#include <minix/type.h>
X#include <fs/const.h>
X#include <fs/type.h>
X#include <fs/super.h>
X
X#include "de.h"
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Read_Disk( state, block_addr, buffer )			*/
X/*								*/
X/*		Reads a 1k block at "block_addr" into "buffer".	*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Read_Disk( s, block_addr, buffer )
X  de_state *s;
X  off_t  block_addr;
X  char  *buffer;
X
X  {
X  if ( lseek( s->device_d, block_addr, SEEK_SET ) == -1 )
X    Error( "Error seeking %s", s->device_name );
X
X  if ( read( s->device_d, buffer, K ) != K )
X    Error( "Error reading %s", s->device_name );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Read_Block( state, buffer )				*/
X/*								*/
X/*		Reads a 1k block from "state->address" into	*/
X/*		"buffer". Checks "address", and updates		*/
X/*		"block" and "offset".				*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Read_Block( s, buffer )
X  de_state *s;
X  char *buffer;
X
X  {
X  off_t end_addr = (long) s->device_size * K - 1;
X  off_t block_addr;
X
X  if ( s->address < 0 )
X    s->address = 0L;
X
X  if ( s->address > end_addr )
X    s->address = end_addr;
X
X  /*  The address must be rounded off for  */
X  /*  certain visual display modes.        */
X
X  if ( s->mode == WORD )
X    s->address &= ~1L;
X  else if ( s->mode == MAP )
X    s->address &= ~3L;
X
X
X  block_addr = s->address & K_MASK;
X
X  s->block  = (unsigned) (block_addr >> K_SHIFT);
X  s->offset = (unsigned) (s->address - block_addr);
X
X  Read_Disk( s, block_addr, buffer );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Read_Super_Block( state )				*/
X/*								*/
X/*		Read and check the super block.			*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Read_Super_Block( s )
X  de_state *s;
X
X  {
X  struct super_block *super = (struct super_block *) s->buffer;
X
X  Read_Disk( s, (long) 1 * K, s->buffer );
X
X  s->inodes = super->s_ninodes;
X  s->zones  = super->s_nzones;
X
X  s->inode_maps   = (s->inodes + MAP_BITS_PER_BLOCK) / MAP_BITS_PER_BLOCK;
X  s->zone_maps    = (s->zones + MAP_BITS_PER_BLOCK - 1) / MAP_BITS_PER_BLOCK;
X	/*  Note: zone_maps may be too large, but we must calculate  */
X	/*  it this way, because this is the way mkfs(1) does it.    */
X
X  s->inode_blocks = (s->inodes + INODES_PER_BLOCK - 1) / INODES_PER_BLOCK;
X  s->first_data   = 2 + s->inode_maps + s->zone_maps + s->inode_blocks;
X
X  s->inodes_in_map = s->inodes + 1;
X  s->zones_in_map  = s->zones + 1 - s->first_data;
X
X  /*
X  if ( s->zones != s->device_size )
X    Warning( "Zone count does not equal device size" );
X  */
X
X  s->device_size = s->zones;
X
X  if ( s->inode_maps != super->s_imap_blocks )
X    Warning( "Corrupted inode map count in super block" );
X
X  if ( s->zone_maps != super->s_zmap_blocks )
X    Warning( "Corrupted zone map count in super block" );
X
X  if ( s->first_data != super->s_firstdatazone )
X    Warning( "Corrupted first data zone count in super block" );
X
X  if ( super->s_log_zone_size != 0 )
X    Warning( "Can not handle multiple blocks per zone" );
X
X  if ( super->s_magic != SUPER_MAGIC )
X    Warning( "Corrupted magic number in super block" );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Read_Bit_Maps( state )					*/
X/*								*/
X/*		Read in the i-node and zone bit maps from the	*/
X/*		specified file system device.			*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Read_Bit_Maps( s )
X  de_state *s;
X
X  {
X  int i;
X
X  if ( s->inode_maps > I_MAP_SLOTS  ||  s->zone_maps > ZMAP_SLOTS )
X    {
X    Warning( "Super block specifies too many bit map blocks" );
X    return;
X    }
X
X  for ( i = 0;  i < s->inode_maps;  ++i )
X    {
X    Read_Disk( s, (long) (2 + i) * K, &s->inode_map[ i * K ] );
X    }
X
X  for ( i = 0;  i < s->zone_maps;  ++i )
X    {
X    Read_Disk( s, (long) (2 + s->inode_maps + i) * K, &s->zone_map[ i * K ] );
X    }
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Search( state, string )					*/
X/*								*/
X/*		Search from the current address for the ASCII	*/
X/*		"string" on the device.				*/
X/*								*/
X/****************************************************************/
---------------------------------------------------------------

holm@ubc-bdcvax.UUCP (Terrence W. Holm) (02/03/89)

---------------------------------------------------------------
X
X
Xoff_t Search( s, string )
X  de_state *s;
X  char *string;
X
X  {
X  off_t address   = s->address + 1;
X  off_t last_addr = address;
X  char  buffer[ SEARCH_BUFFER ];
X  int   offset;
X  int   tail_length = strlen( string ) - 1;
X  int   count = SEARCH_BUFFER;
X  int   last_offset;
X
X
X  for (  ;  count == SEARCH_BUFFER;  address += SEARCH_BUFFER - tail_length )
X    {
X    if ( lseek( s->device_d, address, SEEK_SET ) == -1 )
X      Error( "Error seeking %s", s->device_name );
X
X    if ( (count = read( s->device_d, buffer, SEARCH_BUFFER)) == -1 )
X      Error( "Error reading %s", s->device_name );
X
X
X    if ( address - last_addr >= 500L * K )
X      {
X      putchar( '.' );
X      fflush( stdout );
X
X      last_addr += 500L * K;
X      }
X
X
X    last_offset = count - tail_length;
X
X    for ( offset = 0;  offset < last_offset;  ++offset )
X      {
X      register char c = buffer[ offset ];
X
X      if ( c == *string )
X	{
X	char *tail_buffer = &buffer[ offset + 1 ];
X	char *tail_string = string + 1;
X
X	do
X	  {
X	  if ( *tail_string == '\0' )
X	    return( address + offset );
X	  }
X          while ( *tail_buffer++ == *tail_string++ );
X        }
X      }  /*  end for ( offset )  */
X    }  /*  end for ( address )  */
X
X  return( -1L );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Write_Word( state, word )				*/
X/*								*/
X/*		Write a word at address.			*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Write_Word( s, word )
X  de_state *s;
X  unsigned word;
X
X  {
X  if ( s->address & 01 )
X    Error( "Internal fault (unaligned address)" );
X
X  if ( lseek( s->device_d, s->address, SEEK_SET ) == -1 )
X    Error( "Error seeking %s", s->device_name );
X
X  if ( write( s->device_d, &word, 2 ) != 2 )
X    Error( "Error writing %s", s->device_name );
X  }
/
echo x - de_recover.c
sed '/^X/s///' > de_recover.c << '/'
X/****************************************************************/
X/*								*/
X/*	de_recover.c						*/
X/*								*/
X/*		File restoration routines.			*/
X/*								*/
X/****************************************************************/
X/*  origination         1989-Jan-21        Terrence W. Holm	*/
X/*  handle "holes"	1989-Jan-28	   Terrence W. Holm	*/
X/****************************************************************/
X
X
X#include <sys/types.h>
X#include <sys/dir.h>
X#include <fcntl.h>
X#include <pwd.h>
X#include <stat.h>
X#include <stdio.h>
X#include <string.h>
X#include <unistd.h>
X
X#include <minix/type.h>
X#include <minix/blocksize.h>
X#include <fs/const.h>
X#include <fs/type.h>
X
X#include "de.h"
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Path_Dir_File( path_name, dir_name, file_name )		*/
X/*								*/
X/*		Split "path_name" into a directory name and	*/
X/*		a file name.					*/
X/*								*/
X/*		Zero is returned on error conditions.		*/
X/*								*/
X/****************************************************************/
X
X
Xint Path_Dir_File( path_name, dir_name, file_name )
X  char  *path_name;
X  char **dir_name;
X  char **file_name;
X
X  {
X  char *p;
X  static char directory[ MAX_STRING + 1 ];
X  static char filename[ MAX_STRING + 1 ];
X
X
X  if ( (p = strrchr( path_name, '/' )) == NULL )
X    {
X    strcpy( directory, "." );
X    strcpy( filename, path_name );
X    }
X  else
X    {
X    *directory = '\0';
X    strncat( directory, path_name, p - path_name );
X    strcpy( filename, p + 1 );
X    }
X
X  if ( *directory == '\0' )
X    strcpy( directory, "/" );
X
X  if ( *filename == '\0' )
X    {
X    Warning( "A file name must follow the directory name" );
X    return( 0 );
X    }
X
X  *dir_name  = directory;
X  *file_name = filename;
X
X  return( 1 );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	File_Device( file_name )				*/
X/*								*/
X/*		Return the name of the file system device	*/
X/*		containing the file "file_name".		*/
X/*								*/
X/*		This is used if the "-r" option was specified.	*/
X/*		In this case we have only been given a file	*/
X/*		name, and must determine which file system	*/
X/*		device to open.					*/
X/*								*/
X/*		NULL is returned on error conditions.		*/
X/*								*/
X/****************************************************************/
X
X
X
Xchar *File_Device( file_name )
X  char *file_name;
X
X  {
X  struct stat file_stat;
X  struct stat device_stat;
X  int dev_d;
X  struct direct entry;
X  static char device_name[ DIRSIZ + 1 ];
X
X
X  if ( access( file_name, R_OK ) != 0 )
X    {
X    Warning( "Can not find %s", file_name );
X    return( NULL );
X    }
X
X
X  if ( stat( file_name, &file_stat ) == -1 )
X    {
X    Warning( "Can not stat(2) %s", file_name );
X    return( NULL );
X    }
X
X
X  /*  Open /dev for reading  */
X
X  if ( (dev_d = open( DEV, O_RDONLY )) == -1 )
X    {
X    Warning( "Can not read %s", DEV );
X    return( NULL );
X    }
X
X
X  while ( read( dev_d, (char *) &entry, sizeof(struct direct) )
X				     == sizeof(struct direct) )
X    {
X    if ( entry.d_ino == 0 )
X      continue;
X
X    strcpy( device_name, DEV );
X    strcat( device_name, "/" );
X    strncat( device_name, entry.d_name, DIRSIZ );
X
X    if ( stat( device_name, &device_stat ) == -1 )
X      continue;
X
X    if ( (device_stat.st_mode & S_IFMT) != S_IFBLK )
X      continue;
X
X    if ( file_stat.st_dev == device_stat.st_rdev )
X      {
X      close( dev_d );
X      return( device_name );
X      }
X    }
X
X  close( dev_d );
X
X  Warning( "The device containing file %s is not in %s", file_name, DEV );
X
X  return( NULL );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Find_Deleted_Entry( state, path_name )			*/
X/*								*/
X/*		Split "path_name" into a directory name and	*/
X/*		a file name. Then search the directory for	*/
X/*		an entry that would match the deleted file	*/
X/*		name. (Deleted entries have a zero i-node	*/
X/*		number, but the original i-node number is 	*/
X/*		placed at the end of the file name.)		*/
X/*								*/
X/*		If successful an i-node number is returned,	*/
X/*		else zero is returned.				*/
X/*								*/
X/****************************************************************/
X
X
Xino_t Find_Deleted_Entry( s, path_name )
X  de_state *s;
X  char *path_name;
X
X  {
X  char *dir_name;
X  char *file_name;
X
X
X  /*  Check if the file exists  */
X
X  if ( access( path_name, F_OK ) == 0 )
X    {
X    Warning( "File has not been deleted" );
X    return( 0 );
X    }
X
X
X  /*  Split the path name into a directory and a file name  */
X
X  if ( ! Path_Dir_File( path_name, &dir_name, &file_name ) )
X    return( 0 );
X
X
X  /*  Check to make sure the user has read permission on  */
X  /*  the directory.					  */
X
X  if ( access( dir_name, R_OK ) != 0 )
X    {
X    Warning( "Can not find %s", dir_name );
X    return( 0 );
X    }
X
X
X  /*  Make sure "dir_name" is really a directory. */
X  {
X  struct stat dir_stat;
X
X  if ( stat( dir_name, &dir_stat ) == -1   ||
X		 (dir_stat.st_mode & S_IFMT) != S_IFDIR )
X    {
X    Warning( "Can not find directory %s", dir_name );
X    return( 0 );
X    }
X  }
X
X
X  /*  Make sure the directory is on the current  */
X  /*  file system device.                        */
X
X  if ( Find_Inode( s, dir_name ) == 0 )
X    return( 0 );
X
X
X  /*  Open the directory and search for the lost file name.  */
X  {
X  int   dir_d;
X  int   count;
X  struct direct entry;
X
X  if ( (dir_d = open( dir_name, O_RDONLY )) == -1 )
X    {
X    Warning( "Can not read directory %s", dir_name );
X    return( 0 );
X    }
X
X  while ( (count = read( dir_d, (char *) &entry, sizeof(struct direct) ))
X					      == sizeof(struct direct) )
X    {
X    if ( entry.d_ino == 0  &&
X	strncmp( file_name, entry.d_name, DIRSIZ - sizeof(ino_t) ) == 0 )
X      {
X      ino_t inode = *( (ino_t *) &entry.d_name[ DIRSIZ - sizeof(ino_t) ] );
X
X      close( dir_d );
X
X      if ( inode < 1  || inode > s->inodes )
X    	{
X    	Warning( "Illegal i-node number" );
X    	return( 0 );
X    	}
X
X      return( inode );
X      }
X    }
X
X  close( dir_d );
X
X  if ( count == 0 )
X    Warning( "Can not find a deleted entry for %s", file_name );
X  else
X    Warning( "Problem reading directory %s", dir_name );
X
X  return( 0 );
X  }
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Recover_Blocks( state )					*/
X/*								*/
X/*		Try to recover all the blocks for the i-node	*/
X/*		currently pointed to by "s->address". The	*/
X/*		i-node and all of the blocks must be marked	*/
X/*		as FREE in the bit maps. The owner of the	*/
X/*		i-node must match the current real user name.	*/
X/*								*/
X/*		"Holes" in the original file are maintained.	*/
X/*		This allows moving sparse files from one device	*/
X/*		to another.					*/
X/*								*/
X/*		On any error -1L is returned, otherwise the	*/
X/*		size of the recovered file is returned.		*/
X/*								*/
X/*								*/
X/*		NOTE: Once a user has read access to a device,	*/
X/*		there is a security hole, as we lose the	*/
X/*		normal file system protection. For convenience,	*/
X/*		de(1) is sometimes set-uid root, this allows	*/
X/*		anyone to use the "-r" option. When recovering,	*/
X/*		Recover_Blocks() can only superficially check	*/
X/*		the validity of a request.			*/
X/*								*/
X/****************************************************************/
X
X
Xoff_t Recover_Blocks( s )
X  de_state *s;
X
X  {
X  d_inode *inode = (d_inode *) &s->buffer[ s->offset & ~ PAGE_MASK ];
X  int node = (s->address - (s->first_data - s->inode_blocks) * K) /
X		INODE_SIZE + 1;
X
X
X  if ( s->block < s->first_data - s->inode_blocks  ||
X	    s->block >= s->first_data )
X    {
X    Warning( "Not in an inode block" );
X    return( -1L );
X    }
X
X
X  /*  Is this a valid, but free i-node?  */
X
X  if ( node > s->inodes )
X    {
X    Warning( "Not an inode" );
X    return( -1L );
X    }
X
X  if ( In_Use(node, s->inode_map) )
X    {
X    Warning( "I-node is in use" );
X    return( -1L );
X    }
X
X
X  /*  Only recover files that belonged to the real user.  */
X
X  {
X  uid real_uid = getuid();
X  struct passwd *user = getpwuid( real_uid );
X
X  if ( real_uid != SU_UID  &&  real_uid != inode->i_uid )
X    {
X    Warning( "I-node did not belong to user %s", user ? user->pw_name : "" );
X    return( -1L );
X    }
X  }
X
X
X  /*  Recover all the blocks of the file.  */
X
X  {
X  off_t file_size = inode->i_size;
X  int i;
X
X
X  /*  Up to 7 block pointers are stored in the i-node.  */
X
X  for ( i = 0;  i < NR_DZONE_NUM;  ++i )
X    {
X    if ( file_size == 0 )
X	return( inode->i_size );
X
X    if ( ! Data_Block( s, inode->i_zone[ i ], &file_size ) )
X      return( -1L );
X    }
X
X  if ( file_size == 0 )
X    return( inode->i_size );
X
X
X  /*  An indirect block can contain up to 512 more block pointers.  */
X
X  if ( ! Indirect( s, inode->i_zone[ NR_DZONE_NUM ], &file_size, 0 ) )
X    return( -1L );
X
X  if ( file_size == 0 )
X    return( inode->i_size );
X
X
X  /*  A double indirect block can contain up to 512 indirect blocks.  */
X
X  if ( ! Indirect( s, inode->i_zone[ NR_DZONE_NUM+1 ], &file_size, 1 ) )
X    return( -1L );
X
X  if ( file_size == 0 )
X    return( inode->i_size );
X
X  Error( "Internal fault (file_size != 0)" );
X  }
X  }
X
X
X
X
X
X
X/*  Indirect( state, block, &file_size, double )
X *
X *  Recover all the blocks pointed to by the indirect block
X *  "block",  up to "file_size" bytes. If "double" is true,
X *  then "block" is a double-indirect block pointing to 512
X *  indirect blocks.
X *
X *  If a "hole" is encountered, then just seek ahead in the
X *  output file.
X */
X
X
Xint Indirect( s, block, file_size, double )
X  de_state *s;
X  zone_nr   block;
X  off_t    *file_size;
X  int       double;
X
X  {
X  zone_nr indirect[ NR_INDIRECTS ];
X  int  i;
X
X  /*  Check for a "hole".  */
X
X  if ( block == NO_ZONE )
X    {
X    off_t skip = (off_t) NR_INDIRECTS * K;
X
X    if ( *file_size < skip  ||  double )
X      {
X      Warning( "File has a hole at the end" );
X      return( 0 );
X      }
X
X    if ( fseek( s->file_f, skip, SEEK_CUR ) == -1 )
X      {
X      Warning( "Problem seeking %s", s->file_name );
X      return( 0 );
X      }
X
X    *file_size -= skip;
X    return( 1 );
X    }
X
X
X  /*  Not a "hole". Recover indirect block, if not in use.  */
X
X  if ( ! Free_Block( s, block ) )
X    return( 0 );
X
X
X  Read_Disk( s, (long) block << K_SHIFT, indirect );
X
X  for ( i = 0;  i < NR_INDIRECTS;  ++i )
X    {
X    if ( *file_size == 0 )
X	return( 1 );
X
X    if ( double )
X      {
X      if ( ! Indirect( s, indirect[ i ], file_size, 0 ) )
X	return( 0 );
X      }
X    else
X      {
X      if ( ! Data_Block( s, indirect[ i ], file_size ) )
X        return( 0 );
X      }
X    }
X
X  return( 1 );
X  }
X
X
X
X
X
X
X/*  Data_Block( state, block, &file_size )
X *
X *  If "block" is free then write  Min(file_size, k)
X *  bytes from it onto the current output file.
X *
X *  If "block" is zero, this means that a 1k "hole"
X *  is in the file. The recovered file maintains
X *  the reduced size by not allocating the block.
X *
X *  The file size is decremented accordingly.
X */
X
X
Xint Data_Block( s, block, file_size )
X  de_state *s;
X  zone_nr   block;
X  off_t    *file_size;
X
X  {
X  char buffer[ K ];
X  off_t block_size = *file_size > K ? K : *file_size;
X
X
X  /*  Check for a "hole".  */
X
X  if ( block == NO_ZONE )
X    {
X    if ( block_size < K )
X      {
X      Warning( "File has a hole at the end" );
X      return( 0 );
X      }
X
X    if ( fseek( s->file_f, block_size, SEEK_CUR ) == -1 )
X      {
X      Warning( "Problem seeking %s", s->file_name );
X      return( 0 );
X      }
X
X    *file_size -= block_size;
X    return( 1 );
X    }
X
X
X  /*  Block is not a "hole". Copy it to output file, if not in use.  */
X
X  if ( ! Free_Block( s, block ) )
X    return( 0 );
X
X  Read_Disk( s, (long) block << K_SHIFT, buffer );
X
X
X  if ( fwrite( buffer, 1, (int) block_size, s->file_f ) != (int) block_size )
X    {
X    Warning( "Problem writing %s", s->file_name );
X    return( 0 );
X    }
X
X  *file_size -= block_size;
X  return( 1 );
X  }
X
X
X
X
X
X
X/*  Free_Block( state, block )
X *
X *  Make sure "block" is a valid data block number, and it
X *  has not been allocated to another file.
X */
X
X
Xint Free_Block( s, block )
X  de_state *s;
X  zone_nr  block;
X
X  {
X  if ( block < s->first_data  ||  block >= s->zones )
X    {
X    Warning( "Illegal block number" );
X    return( 0 );
X    }
X
X  if ( In_Use( block - s->first_data + 1, s->zone_map ) )
X    {
X    Warning( "Encountered an \"in use\" data block" );
X    return( 0 );
X    }
X
X  return( 1 );
X  }
X
/
echo x - de_stdin.c
sed '/^X/s///' > de_stdin.c << '/'
X/****************************************************************/
X/*								*/
X/*	de_stdin.c						*/
X/*								*/
X/*		Processing input from the "de" user.		*/
X/*								*/
X/****************************************************************/
X/*  origination         1989-Jan-15        Terrence W. Holm	*/
X/****************************************************************/
X
X
X#include <sys/types.h>
X#include <sgtty.h>
X#include <signal.h>
X#include <stdio.h>
X
X#include <fs/const.h>
X
X#include "de.h"
X
X
X
X/****************************************************************/
X/*								*/
X/*	Save_Term()						*/
X/*								*/
X/*		Save the current terminal characteristics.	*/
X/*								*/
X/*								*/
X/*	Set_Term()						*/
X/*								*/
X/*		Set up the terminal characteristics.		*/
X/*								*/
X/*								*/
X/*	Reset_Term()						*/
X/*								*/
X/*		Restore the terminal characteristics.		*/
X/*								*/
X/****************************************************************/
X
X
Xstatic struct sgttyb saved_mode;
Xstatic struct tchars saved_chars;
X
X
X
Xvoid Save_Term()
X
X  {
X  ioctl( 0, TIOCGETP, &saved_mode  );
X  ioctl( 0, TIOCGETC, &saved_chars );
X  }
X
X
X
X
Xvoid Set_Term()
X
X  {
X  struct sgttyb mode;
X  struct tchars chars;
X
X  mode  = saved_mode;
X  chars = saved_chars;
X
X
X  /*  No tab expansion, no echo, don't map ^M to ^J, cbreak mode  */
X
X  mode.sg_flags = mode.sg_flags & ~XTABS & ~ECHO & ~CRMOD  |  CBREAK;
X
X
X  /*  Change the interrupt character to ^C  */
X
X  chars.t_intrc  = '\003';
X
X  ioctl( 0, TIOCSETP, &mode  );
X  ioctl( 0, TIOCSETC, &chars );
X  }
X
X
X
X
Xvoid Reset_Term()
X
X  {
X  ioctl( 0, TIOCSETP, &saved_mode  );
X  ioctl( 0, TIOCSETC, &saved_chars );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Get_Char()						*/
X/*								*/
X/*		Return the next input character. Escape		*/
X/*		sequences are mapped to special codes.		*/
X/*								*/
X/****************************************************************/
X
X
Xint Get_Char()
X  {
X  int c;
X  static int unget_char = EOF;
X
X
X  /*  Flush the output to the screen before waiting  */
X  /*  for input from the user.			     */
X
X  fflush( stdout );
X
X  if ( unget_char == EOF )
X    {
X    while ( (c = Timed_Get_Char( 60 * 60 )) < EOF )
X      printf( "%c", BELL );
X    }
X  else
X    {
X    c = unget_char;
X    unget_char = EOF;
X    }
X
X  if ( c == EOF )
X    return( EOF );
X
X  if ( c != ESCAPE )
X    return( c );
X
X  if ( (c = Timed_Get_Char( 1 )) <= EOF )
X    return( ESCAPE );
X
X  if ( c != '[' )
X    {
X    unget_char = c;
X    return( ESCAPE );
X    }
X
X  if ( (c = Timed_Get_Char( 1 )) <= EOF )
X    {
X    unget_char = '[';
X    return( ESCAPE );
X    }
X
X  return( c | 0x80 );   /* Flag ESC [ x  */
X  }
X
X
X
X
XTimed_Out()
X  {}
X
X
X
X
Xint Timed_Get_Char( time )
X  int time;
X
X  {
X  char c;
X  int  count;
X
X  signal( SIGALRM, Timed_Out );
X
X  alarm( time );
X  count = read( 0, &c, 1 );
X  alarm( 0 );
X
X  if ( count <= 0 )
X    return( EOF + count );
X
X  return( c & 0x7f );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Get_Line()						*/
X/*								*/
X/*		Read a line from the user. Returns a pointer	*/
X/*		to a local buffer, or NULL if DEL or a non-	*/
X/*		ASCII character was typed. Processes ^H and	*/
X/*		^U. ^M terminates the input.			*/
X/*								*/
X/****************************************************************/
X
X
Xchar *Get_Line()
X
X  {
X  int c;
X  int i;
X  static char line[ MAX_STRING + 1 ];
X
X  for ( i = 0;  i <= MAX_STRING;  ++i )
X    {
X    c = Get_Char();
X
X    if ( c == EOF  ||  c == DEL  ||  (c & 0x80) )
X	return( NULL );
X
X    if ( c == BS )
X	{
X	if ( --i >= 0 )
X	  {
X	  printf( "\b \b" );
X	  --i;
X	  }
X	}
X
X    else if ( c == CTRL_U )
X	{
X	for ( --i;  i >= 0;  --i )
X	  printf( "\b \b" );
X	}
X
X    else if ( c == '\r' )
X	{
X	line[ i ] = '\0';
X	return( line );
X	}
X
X    else if ( i < MAX_STRING )
X	{
X	line[ i ] = c;
X	Print_Ascii( c );
X	}
X
X    else  /*  Line buffer is full, don't add any more to it.  */
X	{
X	putchar( BELL );
X	--i;
X	}
X    }
X
X  Error( "Internal fault (line buffer overflow)" );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Arrow_Esc( char )					*/
X/*								*/
X/*		If the keyboard does not generate Ansi escape	*/
X/*		codes for the arrow keys, but does generate	*/
X/*		single byte control codes, then map these	*/
X/*		codes to the special characters we are using	*/
X/*		to denote the Ansi escape codes.		*/
X/*								*/
X/****************************************************************/
X
X
Xextern  char   Kup;		/* (ku) - Up arrow key		*/
Xextern  char   Kdown;		/* (kd) - Down arrow key	*/
Xextern  char   Kleft;		/* (kl) - Left arrow key	*/
Xextern  char   Kright;		/* (kr) - Right arrow key	*/
X
X
Xint Arrow_Esc( c )
X  int c;
X
X  {
X  if ( c == Kup )
X    return( ESC_UP );
X
X  if ( c == Kdown )
X    return( ESC_DOWN );
X
X  if ( c == Kleft )
X    return( ESC_LEFT );
X
X  if ( c == Kright )
X    return( ESC_RIGHT );
X
X  return( c );
X  }
/
echo x - de_stdout.c
sed '/^X/s///' > de_stdout.c << '/'
X/****************************************************************/
X/*								*/
X/*	de_stdout.c						*/
X/*								*/
X/*		Displaying information from the "Disk editor".	*/
X/*								*/
X/****************************************************************/
X/*  origination         1989-Jan-15        Terrence W. Holm	*/
X/****************************************************************/
X
---------------------------------------------------------------

holm@ubc-bdcvax.UUCP (Terrence W. Holm) (02/03/89)

---------------------------------------------------------------
X
X#include <sys/types.h>
X#include <ar.h>
X#include <fcntl.h>
X#include <grp.h>
X#include <pwd.h>
X#include <stat.h>
X#include <stdio.h>
X
X#include <minix/type.h>
X#include <fs/const.h>
X#include <fs/type.h>
X
X#include "de.h"
X
X
X/****************************************************************/
X/*   		Code for handling termcap			*/
X/****************************************************************/
X
X
X#define  TC_BUFFER  1024	/* Size of termcap(3) buffer	*/
X#define  TC_STRINGS  200	/* Enough room for cm,cl,so,se	*/
X
X
Xstatic  char  *Tmove;		/* (cm) - Format for tgoto	*/
Xstatic  char  *Tclr_all;	/* (cl) - Clear screen  	*/
Xstatic  char  *Treverse;	/* (so) - Start reverse mode 	*/
Xstatic  char  *Tnormal;		/* (se) - End reverse mode	*/
X
Xchar   Kup    = 0;		/* (ku) - Up arrow key		*/
Xchar   Kdown  = 0;		/* (kd) - Down arrow key	*/
Xchar   Kleft  = 0;		/* (kl) - Left arrow key	*/
Xchar   Kright = 0;		/* (kr) - Right arrow key	*/
X
X
X
X/****************************************************************/
X/*								*/
X/*	Init_Termcap()						*/
X/*								*/
X/*		Initializes the external variables for the	*/
X/*		current terminal.				*/
X/*								*/
X/****************************************************************/
X
X
Xint Init_Termcap()
X
X  {
X  char  *term;
X  char   buffer[ TC_BUFFER ];
X  static char strings[ TC_STRINGS ];
X  char  *s = &strings[0];
X  char  *Kcode;
X
X
X  term = getenv( "TERM" );
X
X  if ( term == NULL )
X    return( 0 );
X
X  if ( tgetent( buffer, term ) != 1 )
X    return( 0 );
X
X
X  if ( (Tmove = tgetstr( "cm", &s )) == NULL )
X    return( 0 );
X
X  if ( (Tclr_all = tgetstr( "cl", &s )) == NULL )
X    return( 0 );
X
X  if ( (Treverse = tgetstr( "so", &s )) == NULL )
X    {
X    Treverse = Tnormal = s;
X    *s = '\0';
X    ++s;
X    }
X  else if ( (Tnormal = tgetstr( "se", &s )) == NULL )
X    return( 0 );
X
X
X  /*  See if there are single character arrow key codes  */
X
X  if ( (Kcode = tgetstr( "ku", &s )) != NULL  &&  strlen( Kcode ) == 1 )
X    Kup = Kcode[0];
X
X  if ( (Kcode = tgetstr( "kd", &s )) != NULL  &&  strlen( Kcode ) == 1 )
X    Kdown = Kcode[0];
X
X  if ( (Kcode = tgetstr( "kl", &s )) != NULL  &&  strlen( Kcode ) == 1 )
X    Kleft = Kcode[0];
X
X  if ( (Kcode = tgetstr( "kr", &s )) != NULL  &&  strlen( Kcode ) == 1 )
X    Kright = Kcode[0];
X
X
X  return( 1 );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Goto( column, line )					*/
X/*								*/
X/*		Use the termcap string to move the cursor.	*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Goto( column, line )
X  int  column;
X  int  line;
X
X  {
X  fputs( tgoto( Tmove, column, line ), stdout );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*   		       Output routines				*/
X/****************************************************************/
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Draw_Help_Screen()					*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Draw_Help_Screen( s )
X  de_state *s;
X
X  {
X  int down;
X  int right;
X
X  switch ( s->mode )
X    {
X    case WORD  :   down = 2;    right = 32;  break;
X    case BLOCK :   down = 64;   right = 1;   break;
X    case MAP   :   down = 256;  right = 4;   break;
X    }
X
X  printf( "%s                             ", Tclr_all );
X  printf( "%sDE  COMMANDS%s\r\n\n\n", Treverse, Tnormal );
X
X
X  printf( "   PGUP   b   Back one block              h   Help\r\n" );
X  printf( "   PGDN   f   Forward one block           q   Quit\r\n" );
X  printf( "   HOME   B   Goto first block            m   Minix shell\r\n" );
X  printf( "   END    F   Goto last block\r\n" );
X  printf( "                                          v   Visual mode (w b m)\r\n" );
X  printf( "          g   Goto specified block        o   Output base (h d o b)\r\n" );
X  printf( "          G   Goto block indirectly\r\n" );
X  printf( "          i   Goto i-node                 c   Change file name\r\n" );
X  printf( "          I   Filename to i-node          w   Write ASCII block\r\n" );
X  printf( "                                          W   Write block exactly\r\n" );
X  printf( "          /   Search\r\n" );
X  printf( "          n   Next occurrence             x   Extract lost entry\r\n" );
X  printf( "          p   Previous address            X   Extract lost blocks\r\n" );
X  printf( "                                          s   Store word\r\n" );
X  printf( "   UP     u   Move back %d bytes\r\n", down );
X  printf( "   DOWN   d   Move forward %d bytes\r\n", down );
X  printf( "   LEFT   l   Move back %d byte%s\r\n", right,
X					right == 1 ? "" : "s" );
X  printf( "   RIGHT  r   Move forward %d byte%s\r\n\n\n", right,
X					right == 1 ? "" : "s" );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Wait_For_Key()						*/
X/*								*/
X/*		The user must press a key to continue.		*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Wait_For_Key()
X
X  {
X  Draw_Prompt( "Press a key to continue..." );
X
X  Get_Char();
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Draw_Prompt( string )					*/
X/*								*/
X/*		Write a message in the "prompt" area.		*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Draw_Prompt( string )
X  char  *string;
X
X  {
X  Goto( PROMPT_COLUMN, PROMPT_LINE );
X
X  printf( "%s%s%s ", Treverse, string, Tnormal );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Erase_Prompt()						*/
X/*								*/
X/*		Erase the message in the "prompt" area.		*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Erase_Prompt()
X
X  {
X  Goto( PROMPT_COLUMN, PROMPT_LINE );
X
X  printf( "%77c", ' ' );
X
X  Goto( PROMPT_COLUMN, PROMPT_LINE );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Draw_Screen( state )					*/
X/*								*/
X/*		Redraw everything, except pointers.		*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Draw_Screen( s )
X  de_state *s;
X
X  {
X  fputs( Tclr_all, stdout );
X
X  Draw_Strings( s );
X  Block_Type( s );
X
X  switch ( s->mode )
X    {
X    case WORD :   Draw_Words( s );
X		  Draw_Info( s );
X		  break;
X
X    case BLOCK :  Draw_Block( s->buffer );
X		  break;
X
X    case MAP :	  {
X		  int max_bits = 2 * K;
X
X		  /*  Don't display the bits after the end  */
X		  /*  of the i-node or zone bit maps.	    */
X
X		  if ( s->block == 2 + s->inode_maps - 1 )
X		    max_bits = s->inodes_in_map - 8 * K * (s->inode_maps - 1)
X				 - 8 * (s->offset & ~ MAP_MASK);
X
X		  else if ( s->block == 2 + s->inode_maps + s->zone_maps - 1 )
X		    max_bits = s->zones_in_map - 8 * K * (s->zone_maps - 1)
X				 - 8 * (s->offset & ~ MAP_MASK);
X
X		  if ( max_bits < 0 )
X		      max_bits = 0;
X
X		  Draw_Map( &s->buffer[ s->offset & ~ MAP_MASK ], max_bits );
X		  break;
X		  }
X    }
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Draw_Strings( state )					*/
X/*								*/
X/*		The first status line contains the device name,	*/
X/*		the current write file name (if one is open)	*/
X/*		and the current search string (if one has	*/
X/*		been defined).					*/
X/*								*/
X/*		Long strings are truncated.			*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Draw_Strings( s )
X  de_state *s;
X
X  {
X  int len;
X  int i;
X
X  Goto( STATUS_COLUMN, STATUS_LINE );
X
X  printf( "Device %s= %-14.14s  ",
X	     s->device_mode == O_RDONLY ? "" : "(w) ", s->device_name );
X
X
X  len = strlen( s->file_name );
X
X  if ( len == 0 )
X    printf( "%29s", " " );
X  else if ( len <= 20 )
X    printf( "File = %-20s  ", s->file_name );
X  else
X    printf( "File = ...%17.17s  ", s->file_name + len - 17 );
X
X
X  len = strlen( s->search_string );
X
X  if ( len == 0 )
X    printf( "%20s", " " );
X  else
X    {
X    printf( "Search = " );
X
X    if ( len <= 11 )
X      {
X      for ( i = 0;  i < len;  ++i )
X        Print_Ascii( s->search_string[ i ] );
X
X      for ( ;  i < 11;  ++i )
X	putchar( ' ' );
X      }
X    else
X      {
X      for ( i = 0;  i < 8;  ++i )
X        Print_Ascii( s->search_string[ i ] );
X
X      printf( "..." );
X      }
X    }
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Block_Type( state )					*/
X/*								*/
X/*		Display the current block type.			*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Block_Type( s )
X  de_state *s;
X
X  {
X  Goto( STATUS_COLUMN, STATUS_LINE + 1 );
X
X  printf( "Block  = %5u of %-5u  ", s->block, s->zones );
X
X
X  if ( s->block == BOOT_BLOCK )
X    printf( "Boot block" );
X
X  else if ( s->block == SUPER_BLOCK )
X    printf( "Super block" );
X
X  else if ( s->block < 2 + s->inode_maps )
X    printf( "I-node bit map" );
X
X  else if ( s->block < 2 + s->inode_maps + s->zone_maps )
X    printf( "Zone bit map" );
X
X  else if ( s->block < s->first_data )
X    printf( "I-nodes" );
X
X  else
X    printf( "Data block  (%sin use)",
X	In_Use(s->block - s->first_data + 1, s->zone_map) ? "" : "not " );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Draw_Words( state )					*/
X/*								*/
X/*		Draw a page in word format.			*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Draw_Words( s )
X  de_state *s;
X
X  {
X  int line;
X  int addr = s->offset & ~ PAGE_MASK;
X
X
X  for ( line = 0;  line < 16;  ++line, addr += 2 )
X    {
X    Goto( BLOCK_COLUMN, BLOCK_LINE + line );
X
X    printf( "%5d  ", addr );
X
X    Print_Number( *( (unsigned *) &s->buffer[ addr ] ), s->output_base );
X    }
X
X  Goto( BLOCK_COLUMN + 64, BLOCK_LINE + 6 );
X  printf( "(base %d)", s->output_base );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Draw_Info( state )					*/
X/*								*/
X/*		Add information to a page drawn in word format.	*/
X/*		The routine recognizes the super block, inodes,	*/
X/*		executables and "ar" archives. If the current	*/
X/*		page is not one of these, then ASCII characters	*/
X/*		are printed from the data words.		*/
X/*								*/
X/****************************************************************/
X
X
Xchar *super_block_info[] =  {	"number of inodes",
X				"number of zones",
X				"inode bit map blocks",
X				"zone bit map blocks",
X				"first data zone",
X				"blocks per zone shift",
X				"maximum file size",
X				"",
X				"magic number"  };
X
X
Xchar *inode_info[] =  {	"",
X			"",
X			"",
X			"",
X			"",
X			"",
X			"",
X			"zone 0",
X			"zone 1",
X			"zone 2",
X			"zone 3",
X			"zone 4",
X			"zone 5",
X			"zone 6",
X			"indirect",
X			"double indirect"  };
X
X
X
Xvoid Draw_Info( s )
X  de_state *s;
X
X  {
X  int i;
X  int page = s->offset >> PAGE_SHIFT;
X
X
X  if ( s->block == SUPER_BLOCK  &&  page == 0 )
X      for ( i = 0;  i < 9;  ++i )
X 	{
X	Goto( INFO_COLUMN, INFO_LINE + i );
X	printf( "%s", super_block_info[ i ] );
X	}
X
X  else if ( s->block >= s->first_data - s->inode_blocks  &&
X	    s->block < s->first_data )
X      {
X      d_inode *inode = (d_inode *) &s->buffer[ s->offset & ~ PAGE_MASK ];
X      int special = 0;
X      int m;
X      struct passwd *user = getpwuid( inode->i_uid );
X      struct group  *grp  = getgrgid( inode->i_gid );
X
X      for ( i = 0;  i < 16;  ++i )
X    	{
X    	Goto( INFO_COLUMN, INFO_LINE + i );
X    	printf( "%s", inode_info[ i ] );
X    	}
X
X      Goto( INFO_COLUMN, INFO_LINE  );
X
X      switch( inode->i_mode & S_IFMT )
X    	{
X    	case S_IFDIR :  printf( "directory  " );
X		    	break;
X
X    	case S_IFCHR :  printf( "character  " );
X		    	special = 1;
X		    	break;
X
X    	case S_IFBLK :  printf( "block  " );
X		   	special = 1;
X		    	break;
X
X    	case S_IFREG :  printf( "regular  " );
X		    	break;
X#ifdef S_IFIFO
X    	case S_IFIFO :  printf( "fifo  " );
X		    	break;
X#endif
X    	default      :  printf( "unknown  " );
X    	}
X
X
X      for ( m = 11;  m >= 0;  --m )
X    	putchar( (inode->i_mode & (1<<m)) ? "xwrxwrxwrtgu"[m] : '-' );
X
X      Goto( INFO_COLUMN, INFO_LINE + 1 );
X      printf( "user %s", user ? user->pw_name : "" );
X
X      Goto( INFO_COLUMN, INFO_LINE + 2 );
X      printf( "file size %lu", inode->i_size );
X
X      Goto( INFO_COLUMN, INFO_LINE + 4 );
X      printf( "%s", ctime( &inode->i_modtime ) );
X
X      Goto( INFO_COLUMN, INFO_LINE + 6 );
X      printf( "links %d, group %s", inode->i_nlinks, grp ? grp->gr_name : "" );
X
X      if ( special )
X	{
X        Goto( INFO_COLUMN, INFO_LINE + 7 );
X	printf( "major %d, minor %d", major( inode->i_zone[0] ),
X				      minor( inode->i_zone[0] ) );
X	}
X      }
X
X  else  /*  Print ASCII characters for each byte in page  */
X      {
X      char *p = &s->buffer[ s->offset & ~ PAGE_MASK ];
X
X      for ( i = 0;  i < 16;  ++i )
X        {
X        Goto( INFO_COLUMN, INFO_LINE + i );
X        Print_Ascii( *p++ );
X        Print_Ascii( *p++ );
X        }
X
X      if ( s->block >= s->first_data  &&  page == 0 )
X	{
X	int magic  = (s->buffer[1] << 8) | (s->buffer[0] & 0xff);
X	int second = (s->buffer[3] << 8) | (s->buffer[2] & 0xff);
X
X        /*  Is this block the start of an "ar" archive?  */
X
X	if ( magic == ARMAG )
X	  {
X          Goto( INFO_COLUMN, INFO_LINE );
X	  printf( "\"ar\" archive" );
X	  }
X
X	/*  Is this block the start of an executable file?  */
X
X	else if ( magic == A_OUT )
X	  {
X          Goto( INFO_COLUMN, INFO_LINE );
X	  printf( "executable" );
X
X          Goto( INFO_COLUMN, INFO_LINE + 1 );
X
X	  if ( second == SPLIT )
X	    printf( "separate I & D" );
X	  else
X	    printf( "combined I & D" );
X	  }
X	}
X      }
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Draw_Block( block )					*/
X/*								*/
X/*		Redraw a 1k block in character format.		*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Draw_Block( block )
X  char *block;
X
X  {
X  int line;
X  int column;
X  int reverse = 0;
X  int msb_flag = 0;
X
X
X  for ( line = 0;  line < 16;  ++line )
X    {
X    Goto( BLOCK_COLUMN, BLOCK_LINE + line );
X
X    for ( column = 0;  column < 64;  ++column )
X      {
X      char c = *block++;
X
X      if ( c & 0x80 )
X	{
X	msb_flag = 1;
X	c &= 0x7f;
X	}
X
X      if ( c >= ' '  &&  c < DEL )
X	{
X	if ( reverse )
X	  { fputs( Tnormal, stdout ); reverse = 0; }
X
X        putchar( c );
X	}
X      else
X	{
X	if ( ! reverse )
X	  { fputs( Treverse, stdout ); reverse = 1; }
X
X	putchar( c == DEL ? '?' : '@' + c );
X	}
X      }  /*  end for ( column )  */
X    }  /*  end for ( line )  */
X
X  if ( reverse )
X    { fputs( Tnormal, stdout ); reverse = 0; }
X
X  if ( msb_flag )
X    {
X    Goto( BLOCK_COLUMN + 68, BLOCK_LINE + 6 );
X    fputs( "(MSB)", stdout );
X    }
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Draw_Map( block, max_bits )				*/
X/*								*/
X/*		Redraw a block in a bit map format.		*/
X/*		Display min( max_bits, 2048 ) bits.		*/
X/*								*/
X/*		The 256 bytes in "block" are displayed from	*/
X/*		top to bottom and left to right. Bit 0 of	*/
X/*		a byte is towards the top of the screen.	*/
X/*								*/
X/*		Special graphic codes are used to generate	*/
X/*		two "bits" per character position. So a 16	*/
X/*		line by 64 column display is 32 "bits" by	*/
X/*		64 "bits". Or 4 bytes by 64 bytes.		*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Draw_Map( block, max_bits )
X  char *block;
X  int   max_bits;
X
X  {
X  int line;
X  int column;
X  int bit_count = 0;
X
X  for ( line = 0;  line < 16;  ++line )
X    {
X    char *p = &block[ (line & 0xC) >> 2 ];
X    int shift = (line & 0x3) << 1;
X
X    Goto( BLOCK_COLUMN, BLOCK_LINE + line );
X
X    for ( column = 0;  column < 64;  ++column, p += 4 )
X      {
X      char c = (*p >> shift) & 0x3;
X      int current_bit = ((p - block) << 3) + shift;
X
X      /*  Don't display bits past "max_bits"  */
X
X      if ( current_bit >= max_bits )
X	break;
X
X      /*  If "max_bits" occurs in between the two bits  */
X      /*  I am trying to display as one character, then	*/
X      /*  zero off the high-order bit.			*/
X
X      if ( current_bit + 1 == max_bits )
X	c &= 1;
X
X      switch ( c )
X	{
X	case 0 :  putchar( BOX_CLR );
X		  break;
X
X	case 1 :  putchar( BOX_TOP );
X		  ++bit_count;
X		  break;
X
X	case 2 :  putchar( BOX_BOT );
X		  ++bit_count;
X		  break;
X
X	case 3 :  putchar( BOX_ALL );
X		  bit_count += 2;
X		  break;
X	}
X      }  /*  end for ( column )  */
X    }  /*  end for ( line )  */
X
X
X  Goto( BLOCK_COLUMN + 68, BLOCK_LINE + 6 );
X  printf( "(%d)", bit_count );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Draw_Pointers( state )					*/
X/*								*/
X/*		Redraw the pointers and the offset field.	*/
X/*		The rest of the screen stays intact.		*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Draw_Pointers( s )
X  de_state *s;
X
X  {
X  Draw_Offset( s );
X
X  switch ( s->mode )
X    {
X    case WORD :   Word_Pointers( s->last_addr, s->address );
X		  break;
X
X    case BLOCK :  Block_Pointers( s->last_addr, s->address );
X		  break;
X
X    case MAP :	  Map_Pointers( s->last_addr, s->address );
X		  break;
X    }
X
X  Goto( PROMPT_COLUMN, PROMPT_LINE );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Draw_Offset( state )					*/
X/*								*/
X/*		Display the offset in the current buffer	*/
X/*		and the relative position if within a map	*/
X/*		or i-node block.				*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Draw_Offset( s )
X  de_state *s;
X
X  {
X  Goto( STATUS_COLUMN, STATUS_LINE + 2 );
X
X  printf( "Offset = %5d           ", s->offset );
X
X
X  if ( s->block < 2 )
X    return;
X
X  if ( s->block < 2 + s->inode_maps )
X    {
X    long bit = (s->address - 2 * K) * 8;
X
X    if ( bit < s->inodes_in_map )
X	printf( "I-node %ld of %d     ", bit, s->inodes );
X    else
X	printf( "(padding)                " );
X    }
X
X  else if ( s->block < 2 + s->inode_maps + s->zone_maps )
X    {
X    long bit = (s->address - (2 + s->inode_maps) * K) * 8;
X
X    if ( bit < s->zones_in_map )
X	printf( "Block %ld of %u     ", bit + s->first_data - 1, s->zones );
X    else
X	printf( "(padding)                " );
X    }
X
X  else if ( s->block < s->first_data )
X    {
X    int node = (s->address - (2 + s->inode_maps + s->zone_maps) * K) /
X		INODE_SIZE + 1;
X
X    if ( node <= s->inodes )
X	printf( "I-node %d of %d  (%sin use)       ", node, s->inodes,
X	     In_Use(node, s->inode_map) ? "" : "not " );
X    else
X	printf( "(padding)                             " );
X    }
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Word_Pointers( old_addr, new_addr )			*/
X/*								*/
X/*	Block_Pointers( old_addr, new_addr )			*/
X/*								*/
X/*	Map_Pointers( old_addr, new_addr )			*/
X/*								*/
X/*		Redraw the index pointers for a each type	*/
X/*		of display. The pointer at "old_addr" is	*/
X/*		erased and a new pointer is positioned		*/
X/*		for "new_addr". This makes the screen		*/
X/*		update faster and more pleasant for the user.	*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Word_Pointers( old_addr, new_addr )
X  off_t old_addr;
X  off_t new_addr;
X
X  {
X  int from = ( (int) old_addr & PAGE_MASK ) >> 1;
X  int to   = ( (int) new_addr & PAGE_MASK ) >> 1;
X
X  Goto( BLOCK_COLUMN - 2, BLOCK_LINE + from );
X  putchar( ' ' );
X
X  Goto( BLOCK_COLUMN - 2, BLOCK_LINE + to );
X  putchar( '>' );
X  }
X
X
X
X
Xvoid Block_Pointers( old_addr, new_addr )
X  off_t old_addr;
X  off_t new_addr;
X
X  {
X  int from = (int) old_addr & ~K_MASK;
X  int to   = (int) new_addr & ~K_MASK;
X
X  Goto( BLOCK_COLUMN - 2, BLOCK_LINE + from / 64 );
X  putchar( ' ' );
X
X  Goto( BLOCK_COLUMN - 2, BLOCK_LINE + to / 64 );
X  putchar( '>' );
X
X  Goto( BLOCK_COLUMN + from % 64, BLOCK_LINE + 17 );
X  putchar( ' ' );
X
X  Goto( BLOCK_COLUMN + to % 64, BLOCK_LINE + 17 );
X  putchar( '^' );
X  }
X
X
X
X
Xvoid Map_Pointers( old_addr, new_addr )
X  off_t old_addr;
X  off_t new_addr;
X
X  {
X  int from = ( (int) old_addr & MAP_MASK ) >> 2;
X  int to   = ( (int) new_addr & MAP_MASK ) >> 2;
X
X  Goto( BLOCK_COLUMN + from, BLOCK_LINE + 17 );
X  putchar( ' ' );
X
X  Goto( BLOCK_COLUMN + to, BLOCK_LINE + 17 );
X  putchar( '^' );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Print_Number( number, output_base )			*/
X/*								*/
X/*		Output "number" in the output base.		*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Print_Number( number, output_base )
X  int number;
X  int output_base;
X
X  {
X  switch ( output_base )
X    {
X    case 16 :	printf( "%5x", number );
X		break;
X
X    case 10 :	printf( "%7u", number );
X		break;
X
X    case 8 :	printf( "%7o", number );
X		break;
X
X    case 2 :	{
X      		unsigned int mask;
X      		char pad = ' ';
X
X      		for ( mask = 0x8000;  mask > 1;  mask >>= 1 )
X		  putchar( (mask & number) ? (pad = '0', '1') : pad );
X
X      		putchar( (0x01 & number) ? '1' : '0' );
X
X		break;
X      		}
X
X    default :	Error( "Internal fault (output_base)" );
X    }
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Print_Ascii( char )					*/
X/*								*/
X/*		Display a character in reverse mode if it	*/
X/*		is not a normal printable ASCII character.	*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Print_Ascii( c )
X  char c;
X
X  {
X  c &= 0x7f;
X
X  if ( c < ' ' )
X    printf( "%s%c%s", Treverse, '@' + c, Tnormal );
X  else if ( c == DEL )
X    printf( "%s?%s", Treverse, Tnormal );
X  else
X    putchar( c );
X  }
X
X
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Warning( message, arg1, arg2 )				*/
X/*								*/
X/*		Display a message for 2 seconds.		*/
X/*								*/
X/****************************************************************/
X
X
Xvoid Warning( message, arg1, arg2 )
X  char *message;
X  char *arg1;
X  char *arg2;
X
X  {
X  printf( "%c%s", BELL, Tclr_all );
X
X  Goto( WARNING_COLUMN, WARNING_LINE );
X
X  printf( "%s Warning: ", Treverse );
X  printf( message, arg1, arg2 );
X  printf( " %s", Tnormal );
X
X  sleep( 2 );
X  }
/
echo x - link.cdiff
sed '/^X/s///' > link.cdiff << '/'
X*** link.c.1.3	Wed Jan 18 22:08:09 1989
X--- link.c	Wed Jan 18 22:08:01 1989
X***************
X*** 189,193 ****
X     * be cleared immediately, even though these fields are also cleared by
X     * alloc_inode(). The function wipe_inode() does the dirty work in both cases.
X     */
X!   wipe_inode(rip);
X! }
X--- 189,198 ----
X     * be cleared immediately, even though these fields are also cleared by
X     * alloc_inode(). The function wipe_inode() does the dirty work in both cases.
X     */
X! #ifdef RECOVER
X!   /* Leave zone numbers for de(1) to recover file after an unlink(2).  */
X!   rip->i_dirt = DIRTY;
X! #else
X!   wipe_inode(rip);
X! #endif
X! }
/
echo x - open.cdiff
sed '/^X/s///' > open.cdiff << '/'
X*** open.c.1.3	Mon Aug  1 19:10:58 1988
X--- open.c	Wed Jan 18 22:01:47 1989
X***************
X*** 56,62 ****
X  	/* File exists already. */
X  	switch (rip->i_mode & I_TYPE) {
X  	    case I_REGULAR:		/* truncate regular file */
X! 		if ( (r = forbidden(rip, W_BIT, 0)) == OK) truncate(rip);
X  		break;
X  
X  	    case I_DIRECTORY:	/* can't truncate directory */
X--- 56,69 ----
X  	/* File exists already. */
X  	switch (rip->i_mode & I_TYPE) {
X  	    case I_REGULAR:		/* truncate regular file */
X! #ifdef RECOVER
X! 		if ( (r = forbidden(rip, W_BIT, 0)) == OK) {
X! 			truncate(rip);
X! 			wipe_inode(rip);
X! 		}
X! #else
X! 		if ( (r = forbidden(rip, W_BIT, 0)) == OK) truncate(rip);
X! #endif
X  		break;
X  
X  	    case I_DIRECTORY:	/* can't truncate directory */
/
echo x - path.cdiff
sed '/^X/s///' > path.cdiff << '/'
X*** path.c.1.3	Mon Aug  1 19:10:58 1988
X--- path.c	Wed Jan 18 21:58:21 1989
X***************
X*** 266,271 ****
X--- 266,275 ----
X  				&& cmp_string(dp->d_name, string, NAME_SIZE)) {
X  			/* LOOK_UP or DELETE found what it wanted. */
X  			if (flag == DELETE) {
X+ #ifdef RECOVER
X+ 				*( (inode_nr *) &dp->d_name[ NAME_SIZE - 
X+ 					sizeof(inode_nr) ] ) = dp->d_inum;
X+ #endif
X  				dp->d_inum = 0;	/* erase entry */
X  				bp->b_dirt = DIRTY;
X  				ldir_ptr->i_modtime = clock_time();
/
exit 0
----------------------------------------------------------