612 lines
13 KiB
C
612 lines
13 KiB
C
/****************************************************************/
|
|
/* */
|
|
/* de_recover.c */
|
|
/* */
|
|
/* File restoration routines. */
|
|
/* */
|
|
/****************************************************************/
|
|
/* origination 1989-Jan-21 Terrence W. Holm */
|
|
/* handle "holes" 1989-Jan-28 Terrence W. Holm */
|
|
/****************************************************************/
|
|
|
|
|
|
#include <minix/config.h>
|
|
#include <sys/types.h>
|
|
#include <sys/dir.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <pwd.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <dirent.h>
|
|
|
|
#include <minix/const.h>
|
|
#include <minix/type.h>
|
|
#include "mfs/const.h"
|
|
#include "mfs/type.h"
|
|
#include "mfs/inode.h"
|
|
#include <minix/fslib.h>
|
|
|
|
#include "de.h"
|
|
|
|
_PROTOTYPE(int Indirect, (de_state *s, zone_t block, off_t *size, int dblind));
|
|
_PROTOTYPE(int Data_Block, (de_state *s, zone_t block, off_t *file_size ));
|
|
_PROTOTYPE(int Free_Block, (de_state *s, zone_t block ));
|
|
|
|
|
|
|
|
|
|
/****************************************************************/
|
|
/* */
|
|
/* Path_Dir_File( path_name, dir_name, file_name ) */
|
|
/* */
|
|
/* Split "path_name" into a directory name and */
|
|
/* a file name. */
|
|
/* */
|
|
/* Zero is returned on error conditions. */
|
|
/* */
|
|
/****************************************************************/
|
|
|
|
|
|
int Path_Dir_File( path_name, dir_name, file_name )
|
|
char *path_name;
|
|
char **dir_name;
|
|
char **file_name;
|
|
|
|
{
|
|
char *p;
|
|
static char directory[ MAX_STRING + 1 ];
|
|
static char filename[ MAX_STRING + 1 ];
|
|
|
|
|
|
if ( (p = strrchr( path_name, '/' )) == NULL )
|
|
{
|
|
strcpy( directory, "." );
|
|
strcpy( filename, path_name );
|
|
}
|
|
else
|
|
{
|
|
*directory = '\0';
|
|
strncat( directory, path_name, p - path_name );
|
|
strcpy( filename, p + 1 );
|
|
}
|
|
|
|
if ( *directory == '\0' )
|
|
strcpy( directory, "/" );
|
|
|
|
if ( *filename == '\0' )
|
|
{
|
|
Warning( "A file name must follow the directory name" );
|
|
return( 0 );
|
|
}
|
|
|
|
*dir_name = directory;
|
|
*file_name = filename;
|
|
|
|
return( 1 );
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/****************************************************************/
|
|
/* */
|
|
/* File_Device( file_name ) */
|
|
/* */
|
|
/* Return the name of the file system device */
|
|
/* containing the file "file_name". */
|
|
/* */
|
|
/* This is used if the "-r" option was specified. */
|
|
/* In this case we have only been given a file */
|
|
/* name, and must determine which file system */
|
|
/* device to open. */
|
|
/* */
|
|
/* NULL is returned on error conditions. */
|
|
/* */
|
|
/****************************************************************/
|
|
|
|
|
|
|
|
char *File_Device( file_name )
|
|
char *file_name;
|
|
|
|
{
|
|
struct stat file_stat;
|
|
struct stat device_stat;
|
|
int dev_d;
|
|
struct direct entry;
|
|
static char device_name[ NAME_MAX + 1 ];
|
|
|
|
|
|
if ( access( file_name, R_OK ) != 0 )
|
|
{
|
|
Warning( "Can not find %s", file_name );
|
|
return( NULL );
|
|
}
|
|
|
|
|
|
if ( stat( file_name, &file_stat ) == -1 )
|
|
{
|
|
Warning( "Can not stat(2) %s", file_name );
|
|
return( NULL );
|
|
}
|
|
|
|
|
|
/* Open /dev for reading */
|
|
|
|
if ( (dev_d = open( DEV, O_RDONLY )) == -1 )
|
|
{
|
|
Warning( "Can not read %s", DEV );
|
|
return( NULL );
|
|
}
|
|
|
|
|
|
while ( read( dev_d, (char *) &entry, sizeof(struct direct) )
|
|
== sizeof(struct direct) )
|
|
{
|
|
if ( entry.d_ino == 0 )
|
|
continue;
|
|
|
|
strcpy( device_name, DEV );
|
|
strcat( device_name, "/" );
|
|
strncat( device_name, entry.d_name, NAME_MAX );
|
|
|
|
if ( stat( device_name, &device_stat ) == -1 )
|
|
continue;
|
|
|
|
if ( (device_stat.st_mode & S_IFMT) != S_IFBLK )
|
|
continue;
|
|
|
|
if ( file_stat.st_dev == device_stat.st_rdev )
|
|
{
|
|
close( dev_d );
|
|
return( device_name );
|
|
}
|
|
}
|
|
|
|
close( dev_d );
|
|
|
|
Warning( "The device containing file %s is not in %s", file_name, DEV );
|
|
|
|
return( NULL );
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/****************************************************************/
|
|
/* */
|
|
/* Find_Deleted_Entry( state, path_name ) */
|
|
/* */
|
|
/* Split "path_name" into a directory name and */
|
|
/* a file name. Then search the directory for */
|
|
/* an entry that would match the deleted file */
|
|
/* name. (Deleted entries have a zero i-node */
|
|
/* number, but the original i-node number is */
|
|
/* placed at the end of the file name.) */
|
|
/* */
|
|
/* If successful an i-node number is returned, */
|
|
/* else zero is returned. */
|
|
/* */
|
|
/****************************************************************/
|
|
|
|
|
|
ino_t Find_Deleted_Entry( s, path_name )
|
|
de_state *s;
|
|
char *path_name;
|
|
|
|
{
|
|
char *dir_name;
|
|
char *file_name;
|
|
|
|
|
|
/* Check if the file exists */
|
|
|
|
if ( access( path_name, F_OK ) == 0 )
|
|
{
|
|
Warning( "File has not been deleted" );
|
|
return( 0 );
|
|
}
|
|
|
|
|
|
/* Split the path name into a directory and a file name */
|
|
|
|
if ( ! Path_Dir_File( path_name, &dir_name, &file_name ) )
|
|
return( 0 );
|
|
|
|
|
|
/* Check to make sure the user has read permission on */
|
|
/* the directory. */
|
|
|
|
if ( access( dir_name, R_OK ) != 0 )
|
|
{
|
|
Warning( "Can not find %s", dir_name );
|
|
return( 0 );
|
|
}
|
|
|
|
|
|
/* Make sure "dir_name" is really a directory. */
|
|
{
|
|
struct stat dir_stat;
|
|
|
|
if ( stat( dir_name, &dir_stat ) == -1 ||
|
|
(dir_stat.st_mode & S_IFMT) != S_IFDIR )
|
|
{
|
|
Warning( "Can not find directory %s", dir_name );
|
|
return( 0 );
|
|
}
|
|
}
|
|
|
|
|
|
/* Make sure the directory is on the current */
|
|
/* file system device. */
|
|
|
|
if ( Find_Inode( s, dir_name ) == 0 )
|
|
return( 0 );
|
|
|
|
|
|
/* Open the directory and search for the lost file name. */
|
|
{
|
|
int dir_d;
|
|
int count;
|
|
struct direct entry;
|
|
|
|
if ( (dir_d = open( dir_name, O_RDONLY )) == -1 )
|
|
{
|
|
Warning( "Can not read directory %s", dir_name );
|
|
return( 0 );
|
|
}
|
|
|
|
while ( (count = read( dir_d, (char *) &entry, sizeof(struct direct) ))
|
|
== sizeof(struct direct) )
|
|
{
|
|
if ( entry.d_ino == 0 &&
|
|
strncmp( file_name, entry.d_name, NAME_MAX - sizeof(ino_t) ) == 0 )
|
|
{
|
|
ino_t inode = *( (ino_t *) &entry.d_name[ NAME_MAX - sizeof(ino_t) ] );
|
|
|
|
close( dir_d );
|
|
|
|
if ( inode < 1 || inode > s->inodes )
|
|
{
|
|
Warning( "Illegal i-node number" );
|
|
return( 0 );
|
|
}
|
|
|
|
return( inode );
|
|
}
|
|
}
|
|
|
|
close( dir_d );
|
|
|
|
if ( count == 0 )
|
|
Warning( "Can not find a deleted entry for %s", file_name );
|
|
else
|
|
Warning( "Problem reading directory %s", dir_name );
|
|
|
|
return( 0 );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/****************************************************************/
|
|
/* */
|
|
/* Recover_Blocks( state ) */
|
|
/* */
|
|
/* Try to recover all the blocks for the i-node */
|
|
/* currently pointed to by "s->address". The */
|
|
/* i-node and all of the blocks must be marked */
|
|
/* as FREE in the bit maps. The owner of the */
|
|
/* i-node must match the current real user name. */
|
|
/* */
|
|
/* "Holes" in the original file are maintained. */
|
|
/* This allows moving sparse files from one device */
|
|
/* to another. */
|
|
/* */
|
|
/* On any error -1L is returned, otherwise the */
|
|
/* size of the recovered file is returned. */
|
|
/* */
|
|
/* */
|
|
/* NOTE: Once a user has read access to a device, */
|
|
/* there is a security hole, as we lose the */
|
|
/* normal file system protection. For convenience, */
|
|
/* de(1) is sometimes set-uid root, this allows */
|
|
/* anyone to use the "-r" option. When recovering, */
|
|
/* Recover_Blocks() can only superficially check */
|
|
/* the validity of a request. */
|
|
/* */
|
|
/****************************************************************/
|
|
|
|
|
|
off_t Recover_Blocks( s )
|
|
de_state *s;
|
|
|
|
{
|
|
struct inode core_inode;
|
|
d1_inode *dip1;
|
|
d2_inode *dip2;
|
|
struct inode *inode = &core_inode;
|
|
bit_t node = (s->address - (s->first_data - s->inode_blocks) * K) /
|
|
s->inode_size + 1;
|
|
|
|
dip1 = (d1_inode *) &s->buffer[ s->offset & ~ PAGE_MASK ];
|
|
dip2 = (d2_inode *) &s->buffer[ s->offset & ~ PAGE_MASK
|
|
& ~ (V2_INODE_SIZE-1) ];
|
|
conv_inode( inode, dip1, dip2, READING, s->magic );
|
|
|
|
if ( s->block < s->first_data - s->inode_blocks ||
|
|
s->block >= s->first_data )
|
|
{
|
|
Warning( "Not in an inode block" );
|
|
return( -1L );
|
|
}
|
|
|
|
|
|
/* Is this a valid, but free i-node? */
|
|
|
|
if ( node > s->inodes )
|
|
{
|
|
Warning( "Not an inode" );
|
|
return( -1L );
|
|
}
|
|
|
|
if ( In_Use(node, s->inode_map) )
|
|
{
|
|
Warning( "I-node is in use" );
|
|
return( -1L );
|
|
}
|
|
|
|
|
|
/* Only recover files that belonged to the real user. */
|
|
|
|
{
|
|
uid_t real_uid = getuid();
|
|
struct passwd *user = getpwuid( real_uid );
|
|
|
|
if ( real_uid != SU_UID && real_uid != inode->i_uid )
|
|
{
|
|
Warning( "I-node did not belong to user %s", user ? user->pw_name : "" );
|
|
return( -1L );
|
|
}
|
|
}
|
|
|
|
|
|
/* Recover all the blocks of the file. */
|
|
|
|
{
|
|
off_t file_size = inode->i_size;
|
|
int i;
|
|
|
|
|
|
/* Up to s->ndzones pointers are stored in the i-node. */
|
|
|
|
for ( i = 0; i < s->ndzones; ++i )
|
|
{
|
|
if ( file_size == 0 )
|
|
return( inode->i_size );
|
|
|
|
if ( ! Data_Block( s, inode->i_zone[ i ], &file_size ) )
|
|
return( -1L );
|
|
}
|
|
|
|
if ( file_size == 0 )
|
|
return( inode->i_size );
|
|
|
|
|
|
/* An indirect block can contain up to inode->i_indirects more blk ptrs. */
|
|
|
|
if ( ! Indirect( s, inode->i_zone[ s->ndzones ], &file_size, 0 ) )
|
|
return( -1L );
|
|
|
|
if ( file_size == 0 )
|
|
return( inode->i_size );
|
|
|
|
|
|
/* A double indirect block can contain up to inode->i_indirects blk ptrs. */
|
|
|
|
if ( ! Indirect( s, inode->i_zone[ s->ndzones+1 ], &file_size, 1 ) )
|
|
return( -1L );
|
|
|
|
if ( file_size == 0 )
|
|
return( inode->i_size );
|
|
|
|
Error( "Internal fault (file_size != 0)" );
|
|
}
|
|
|
|
/* NOTREACHED */
|
|
return( -1L );
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Indirect( state, block, &file_size, double )
|
|
*
|
|
* Recover all the blocks pointed to by the indirect block
|
|
* "block", up to "file_size" bytes. If "double" is true,
|
|
* then "block" is a double-indirect block pointing to
|
|
* V*_INDIRECTS indirect blocks.
|
|
*
|
|
* If a "hole" is encountered, then just seek ahead in the
|
|
* output file.
|
|
*/
|
|
|
|
|
|
int Indirect( s, block, file_size, dblind )
|
|
de_state *s;
|
|
zone_t block;
|
|
off_t *file_size;
|
|
int dblind;
|
|
|
|
{
|
|
union
|
|
{
|
|
zone1_t ind1[ V1_INDIRECTS ];
|
|
zone_t ind2[ V2_INDIRECTS(_MAX_BLOCK_SIZE) ];
|
|
} indirect;
|
|
int i;
|
|
zone_t zone;
|
|
|
|
/* Check for a "hole". */
|
|
|
|
if ( block == NO_ZONE )
|
|
{
|
|
off_t skip = (off_t) s->nr_indirects * K;
|
|
|
|
if ( *file_size < skip || dblind )
|
|
{
|
|
Warning( "File has a hole at the end" );
|
|
return( 0 );
|
|
}
|
|
|
|
if ( fseek( s->file_f, skip, SEEK_CUR ) == -1 )
|
|
{
|
|
Warning( "Problem seeking %s", s->file_name );
|
|
return( 0 );
|
|
}
|
|
|
|
*file_size -= skip;
|
|
return( 1 );
|
|
}
|
|
|
|
|
|
/* Not a "hole". Recover indirect block, if not in use. */
|
|
|
|
if ( ! Free_Block( s, block ) )
|
|
return( 0 );
|
|
|
|
|
|
Read_Disk( s, (long) block << K_SHIFT, (char *) &indirect );
|
|
|
|
for ( i = 0; i < s->nr_indirects; ++i )
|
|
{
|
|
if ( *file_size == 0 )
|
|
return( 1 );
|
|
|
|
zone = (s->v1 ? indirect.ind1[ i ] : indirect.ind2[ i ]);
|
|
if ( dblind )
|
|
{
|
|
if ( ! Indirect( s, zone, file_size, 0 ) )
|
|
return( 0 );
|
|
}
|
|
else
|
|
{
|
|
if ( ! Data_Block( s, zone, file_size ) )
|
|
return( 0 );
|
|
}
|
|
}
|
|
|
|
return( 1 );
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Data_Block( state, block, &file_size )
|
|
*
|
|
* If "block" is free then write Min(file_size, k)
|
|
* bytes from it onto the current output file.
|
|
*
|
|
* If "block" is zero, this means that a 1k "hole"
|
|
* is in the file. The recovered file maintains
|
|
* the reduced size by not allocating the block.
|
|
*
|
|
* The file size is decremented accordingly.
|
|
*/
|
|
|
|
|
|
int Data_Block( s, block, file_size )
|
|
de_state *s;
|
|
zone_t block;
|
|
off_t *file_size;
|
|
|
|
{
|
|
char buffer[ K ];
|
|
off_t block_size = *file_size > K ? K : *file_size;
|
|
|
|
|
|
/* Check for a "hole". */
|
|
|
|
if ( block == NO_ZONE )
|
|
{
|
|
if ( block_size < K )
|
|
{
|
|
Warning( "File has a hole at the end" );
|
|
return( 0 );
|
|
}
|
|
|
|
if ( fseek( s->file_f, block_size, SEEK_CUR ) == -1 )
|
|
{
|
|
Warning( "Problem seeking %s", s->file_name );
|
|
return( 0 );
|
|
}
|
|
|
|
*file_size -= block_size;
|
|
return( 1 );
|
|
}
|
|
|
|
|
|
/* Block is not a "hole". Copy it to output file, if not in use. */
|
|
|
|
if ( ! Free_Block( s, block ) )
|
|
return( 0 );
|
|
|
|
Read_Disk( s, (long) block << K_SHIFT, buffer );
|
|
|
|
|
|
if ( fwrite( buffer, 1, (size_t) block_size, s->file_f )
|
|
!= (size_t) block_size )
|
|
{
|
|
Warning( "Problem writing %s", s->file_name );
|
|
return( 0 );
|
|
}
|
|
|
|
*file_size -= block_size;
|
|
return( 1 );
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Free_Block( state, block )
|
|
*
|
|
* Make sure "block" is a valid data block number, and it
|
|
* has not been allocated to another file.
|
|
*/
|
|
|
|
|
|
int Free_Block( s, block )
|
|
de_state *s;
|
|
zone_t block;
|
|
|
|
{
|
|
if ( block < s->first_data || block >= s->zones )
|
|
{
|
|
Warning( "Illegal block number" );
|
|
return( 0 );
|
|
}
|
|
|
|
if ( In_Use( (bit_t) (block - (s->first_data - 1)), s->zone_map ) )
|
|
{
|
|
Warning( "Encountered an \"in use\" data block" );
|
|
return( 0 );
|
|
}
|
|
|
|
return( 1 );
|
|
}
|
|
|