testdisk/src/dirn.c

654 lines
19 KiB
C

/*
File: dirn.c
Copyright (C) 1998-2009 Christophe GRENIER <grenier@cgsecurity.org>
This software is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write the Free Software Foundation, Inc., 51
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifdef HAVE_NCURSES
#include <stdio.h>
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_TIME_H
#include <time.h>
#endif
#include "types.h"
#include "common.h"
#include "intrf.h"
#include "intrfn.h"
#include "dir.h"
#include "log.h"
#include "log_part.h"
#include "dirn.h"
#include "askloc.h"
#include "setdate.h"
extern const char *monstr[];
static void copy_dir(WINDOW *window, disk_t *disk, const partition_t *partition, dir_data_t *dir_data, const file_info_t *dir, unsigned int *copy_ok, unsigned int *copy_bad);
#define INTER_DIR (LINES-25+15)
static void copy_progress(WINDOW *window, const unsigned int copy_ok, const unsigned int copy_bad)
{
static time_t prev_time=0;
const time_t tmp=time(NULL);
if(tmp!=prev_time)
{
prev_time=tmp;
wmove(window,5,0);
wclrtoeol(window);
if(has_colors())
{
if(copy_bad > 0)
wbkgdset(window,' ' | A_BOLD | COLOR_PAIR(1));
else
wbkgdset(window,' ' | A_BOLD | COLOR_PAIR(2));
}
wprintw(window,"Copying, please wait... %u ok, %u failed", copy_ok, copy_bad);
if(has_colors())
wbkgdset(window,' ' | COLOR_PAIR(0));
wrefresh(window);
}
}
static void copy_done(WINDOW *window, const unsigned int copy_ok, const unsigned int copy_bad)
{
wmove(window,5,0);
wclrtoeol(window);
if(has_colors())
{
if(copy_bad > 0)
wbkgdset(window,' ' | A_BOLD | COLOR_PAIR(1));
else
wbkgdset(window,' ' | A_BOLD | COLOR_PAIR(2));
}
wprintw(window,"Copy done! %u ok, %u failed", copy_ok, copy_bad);
if(has_colors())
wbkgdset(window,' ' | COLOR_PAIR(0));
wrefresh(window);
}
static long int dir_aff_ncurses(disk_t *disk, const partition_t *partition, dir_data_t *dir_data, file_info_t*dir_list, const unsigned long int inode, const unsigned int depth)
{
/* Return value
* -1: quit
* 1: back
* other: new inode
* */
int quit=0;
int ask_destination=1;
WINDOW *window=(WINDOW*)dir_data->display;
do
{
int offset=0;
int pos_num=0;
struct td_list_head *pos=dir_list->list.next;
const int old_LINES=LINES;
unsigned int status=FILE_STATUS_MARKED;
aff_copy(window);
wmove(window,3,0);
aff_part(window, AFF_PART_ORDER|AFF_PART_STATUS, disk, partition);
wmove(window,4,0);
wprintw(window,"Directory %s\n",dir_data->current_directory);
do
{
int i;
int car;
struct td_list_head *file_walker = NULL;
for(i=0, file_walker=dir_list->list.next;
file_walker!=&dir_list->list && i<offset;
file_walker=file_walker->next,i++);
for(i=offset;
file_walker!=&dir_list->list && (i-offset)<INTER_DIR;
file_walker=file_walker->next,i++)
{
const file_info_t *current_file=td_list_entry(file_walker, file_info_t, list);
char str[11];
char datestr[80];
wmove(window, 6+i-offset, 0);
wclrtoeol(window); /* before addstr for BSD compatibility */
if(&current_file->list==pos)
{
wattrset(window, A_REVERSE);
waddstr(window, ">");
}
else
waddstr(window, " ");
if((current_file->status&FILE_STATUS_DELETED)!=0 && has_colors())
wbkgdset(window,' ' | COLOR_PAIR(1));
else if((current_file->status&FILE_STATUS_MARKED)!=0 && has_colors())
wbkgdset(window,' ' | COLOR_PAIR(2));
{
const struct tm *tm_p;
if(current_file->td_mtime!=0 && (tm_p = localtime(&current_file->td_mtime))!=NULL)
{
snprintf(datestr, sizeof(datestr),"%2d-%s-%4d %02d:%02d",
tm_p->tm_mday, monstr[tm_p->tm_mon],
1900 + tm_p->tm_year, tm_p->tm_hour,
tm_p->tm_min);
/* May have to use %d instead of %e */
} else {
strncpy(datestr, " ",sizeof(datestr));
}
}
mode_string(current_file->st_mode, str);
wprintw(window, "%s %5u %5u ",
str, (unsigned int)current_file->st_uid, (unsigned int)current_file->st_gid);
wprintw(window, "%9llu", (long long unsigned int)current_file->st_size);
/* screen may overlap due to long filename */
wprintw(window, " %s %s", datestr, current_file->name);
if(((current_file->status&FILE_STATUS_DELETED)!=0 ||
(current_file->status&FILE_STATUS_MARKED)!=0) && has_colors())
wbkgdset(window,' ' | COLOR_PAIR(0));
if(&current_file->list==pos)
wattroff(window, A_REVERSE);
}
wmove(window, 6-1, 51);
wclrtoeol(window);
if(offset>0)
wprintw(window, "Previous");
/* Clear the last line, useful if overlapping */
wmove(window,6+i-offset,0);
wclrtoeol(window);
wmove(window, 6+INTER_DIR, 51);
wclrtoeol(window);
if(file_walker->next!=&dir_list->list)
wprintw(window, "Next");
if(td_list_empty(&dir_list->list))
{
wmove(window,6,0);
wprintw(window,"No file found, filesystem may be damaged.");
}
/* Redraw the bottom of the screen everytime because very long filenames may have corrupt it*/
mvwaddstr(window,LINES-3,0,"Use ");
if(depth>0)
{
if(has_colors())
wbkgdset(window,' ' | A_BOLD | COLOR_PAIR(0));
waddstr(window, "Left");
if(has_colors())
wbkgdset(window,' ' | COLOR_PAIR(0));
waddstr(window," arrow to go back, ");
}
if(has_colors())
wbkgdset(window,' ' | A_BOLD | COLOR_PAIR(0));
waddstr(window,"Right");
if(has_colors())
wbkgdset(window,' ' | COLOR_PAIR(0));
waddstr(window," to change directory");
if((dir_data->capabilities&CAPA_LIST_DELETED)!=0)
{
waddstr(window,", ");
if(has_colors())
wbkgdset(window,' ' | A_BOLD | COLOR_PAIR(0));
waddstr(window,"h");
if(has_colors())
wbkgdset(window,' ' | COLOR_PAIR(0));
if((dir_data->param&FLAG_LIST_DELETED)==0)
waddstr(window," to unhide deleted files");
else
waddstr(window," to hide deleted files");
}
else if((dir_data->capabilities&CAPA_LIST_ADS)!=0)
{
waddstr(window,", ");
if(has_colors())
wbkgdset(window,' ' | A_BOLD | COLOR_PAIR(0));
waddstr(window,"h");
if(has_colors())
wbkgdset(window,' ' | COLOR_PAIR(0));
if((dir_data->param&FLAG_LIST_ADS)==0)
waddstr(window," to unhide Alternate Data Stream");
else
waddstr(window," to hide Alternate Data Stream");
}
wmove(window,LINES-2,4);
if(has_colors())
wbkgdset(window,' ' | A_BOLD | COLOR_PAIR(0));
waddstr(window,"q");
if(has_colors())
wbkgdset(window,' ' | COLOR_PAIR(0));
waddstr(window," to quit");
if(dir_data->copy_file!=NULL)
{
waddstr(window,", ");
if(has_colors())
wbkgdset(window,' ' | A_BOLD | COLOR_PAIR(0));
waddstr(window,":");
if(has_colors())
wbkgdset(window,' ' | COLOR_PAIR(0));
waddstr(window," to select the current file, ");
if(has_colors())
wbkgdset(window,' ' | A_BOLD | COLOR_PAIR(0));
waddstr(window,"a");
if(has_colors())
wbkgdset(window,' ' | COLOR_PAIR(0));
if((status&FILE_STATUS_MARKED)==FILE_STATUS_MARKED)
waddstr(window," to select all files ");
else
waddstr(window," to deselect all files");
if(has_colors())
wbkgdset(window,' ' | A_BOLD | COLOR_PAIR(0));
mvwaddstr(window,LINES-1,4,"C");
if(has_colors())
wbkgdset(window,' ' | COLOR_PAIR(0));
waddstr(window," to copy the selected files, ");
if(has_colors())
wbkgdset(window,' ' | A_BOLD | COLOR_PAIR(0));
waddstr(window,"c");
if(has_colors())
wbkgdset(window,' ' | COLOR_PAIR(0));
waddstr(window," to copy the current file");
}
wrefresh(window);
/* Using gnome terminal under FC3, TERM=xterm, the screen is not always correct */
wredrawln(window,0,getmaxy(window)); /* redrawwin def is boggus in pdcur24 */
car=wgetch(window);
wmove(window,5,0);
wclrtoeol(window);
switch(car)
{
case key_ESC:
case 'q':
case 'M':
quit=1;
break;
case '-':
case KEY_LEFT:
case '4':
if(depth>0)
return 1;
break;
case 'h':
if((dir_data->capabilities&CAPA_LIST_DELETED)!=0)
dir_data->param^=FLAG_LIST_DELETED;
else if((dir_data->capabilities&CAPA_LIST_ADS)!=0)
dir_data->param^=FLAG_LIST_ADS;
return inode;
case 0x0c: /* ctrl+L */
touchwin(stdscr);
touchwin(window);
wrefresh(window);
break;
}
if(!td_list_empty(&dir_list->list))
{
switch(car)
{
case KEY_UP:
case '8':
if(pos->prev!=&dir_list->list)
{
pos=pos->prev;
pos_num--;
}
break;
case KEY_DOWN:
case '2':
if(pos->next!=&dir_list->list)
{
pos=pos->next;
pos_num++;
}
break;
case ':':
{
file_info_t *selected_file;
selected_file=td_list_entry(pos, file_info_t, list);
if(!(selected_file->name[0]=='.' && selected_file->name[1]=='\0') &&
!(selected_file->name[0]=='.' && selected_file->name[1]=='.' && selected_file->name[2]=='\0'))
selected_file->status^=FILE_STATUS_MARKED;
}
if(pos->next!=&dir_list->list)
{
pos=pos->next;
pos_num++;
}
break;
case 'a':
{
struct td_list_head *tmpw= NULL;
td_list_for_each(tmpw, &dir_list->list)
{
file_info_t *tmp=td_list_entry(tmpw, file_info_t, list);
if((tmp->name[0]=='.' && tmp->name[1]=='\0') ||
(tmp->name[0]=='.' && tmp->name[1]=='.' && tmp->name[2]=='\0'))
{
tmp->status&=~FILE_STATUS_MARKED;
}
else
{
if((tmp->status & FILE_STATUS_MARKED)!=status)
tmp->status^=FILE_STATUS_MARKED;
}
}
status^=FILE_STATUS_MARKED;
}
break;
case 'p':
case 'P':
case '+':
case ' ':
case KEY_RIGHT:
case '\r':
case '\n':
case '6':
case KEY_ENTER:
#ifdef PADENTER
case PADENTER:
#endif
{
file_info_t *tmp=td_list_entry(pos, file_info_t, list);
if(pos!=&dir_list->list && (LINUX_S_ISDIR(tmp->st_mode)!=0))
{
const unsigned long int new_inode=tmp->st_ino;
if((new_inode!=inode) &&(strcmp(tmp->name,".")!=0))
{
if(strcmp(tmp->name,"..")==0)
return 1;
if(strlen(dir_data->current_directory)+1+strlen(tmp->name)+1<=sizeof(dir_data->current_directory))
{
if(strcmp(dir_data->current_directory,"/"))
strcat(dir_data->current_directory,"/");
strcat(dir_data->current_directory,tmp->name);
return (long int)new_inode;
}
}
}
}
break;
case KEY_PPAGE:
for(i=0; i<INTER_DIR-1 && pos->prev!=&dir_list->list; i++)
{
pos=pos->prev;
pos_num--;
}
break;
case KEY_NPAGE:
for(i=0; i<INTER_DIR-1 && pos->next!=&dir_list->list; i++)
{
pos=pos->next;
pos_num++;
}
break;
case 'c':
if(dir_data->copy_file!=NULL)
{
const unsigned int current_directory_namelength=strlen(dir_data->current_directory);
file_info_t *tmp=td_list_entry(pos, file_info_t, list);
if(pos!=&dir_list->list &&
strcmp(tmp->name,"..")!=0 &&
current_directory_namelength+1+strlen(tmp->name)<sizeof(dir_data->current_directory)-1)
{
if(strcmp(dir_data->current_directory,"/"))
strcat(dir_data->current_directory,"/");
if(strcmp(tmp->name,".")!=0)
strcat(dir_data->current_directory,tmp->name);
if(dir_data->local_dir==NULL || ask_destination>0)
{
char *local_dir=dir_data->local_dir;
if(LINUX_S_ISDIR(tmp->st_mode)!=0)
dir_data->local_dir=ask_location("Please select a destination where %s and any files below will be copied.",
dir_data->current_directory, local_dir);
else
dir_data->local_dir=ask_location("Please select a destination where %s will be copied.",
dir_data->current_directory, local_dir);
free(local_dir);
ask_destination=0;
}
if(dir_data->local_dir!=NULL)
{
unsigned int copy_bad=0;
unsigned int copy_ok=0;
if(LINUX_S_ISDIR(tmp->st_mode)!=0)
{
copy_dir(window, disk, partition, dir_data, tmp, &copy_ok, &copy_bad);
}
else if(LINUX_S_ISREG(tmp->st_mode)!=0)
{
copy_progress(window, copy_ok, copy_bad);
if(dir_data->copy_file(disk, partition, dir_data, tmp)==0)
copy_ok++;
else
copy_bad++;
}
copy_done(window, copy_ok, copy_bad);
}
dir_data->current_directory[current_directory_namelength]='\0';
}
}
break;
case 'C':
if(dir_data->copy_file!=NULL)
{
if(dir_data->local_dir==NULL || ask_destination>0)
{
char *local_dir=dir_data->local_dir;
dir_data->local_dir=ask_location("Please select a destination where the marked files will be copied.", NULL, local_dir);
free(local_dir);
ask_destination=0;
}
if(dir_data->local_dir!=NULL)
{
unsigned int copy_bad=0;
unsigned int copy_ok=0;
const unsigned int current_directory_namelength=strlen(dir_data->current_directory);
struct td_list_head *tmpw= NULL;
td_list_for_each(tmpw, &dir_list->list)
{
file_info_t *tmp=td_list_entry(tmpw, file_info_t, list);
if((tmp->status&FILE_STATUS_MARKED)!=0 &&
current_directory_namelength + 1 + strlen(tmp->name) <
sizeof(dir_data->current_directory)-1)
{
tmp->status&=~FILE_STATUS_MARKED;
if(strcmp(dir_data->current_directory,"/"))
strcat(dir_data->current_directory,"/");
if(strcmp(tmp->name,".")!=0)
strcat(dir_data->current_directory,tmp->name);
if(LINUX_S_ISDIR(tmp->st_mode)!=0)
{
copy_dir(window, disk, partition, dir_data, tmp, &copy_ok, &copy_bad);
}
else if(LINUX_S_ISREG(tmp->st_mode)!=0)
{
copy_progress(window, copy_ok, copy_bad);
if(dir_data->copy_file(disk, partition, dir_data, tmp) == 0)
copy_ok++;
else
copy_bad++;
}
}
dir_data->current_directory[current_directory_namelength]='\0';
}
copy_done(window, copy_ok, copy_bad);
}
}
break;
case 'f':
{
const char *needle=ask_string_ncurses("Filename to find ? ");
if(needle!=NULL && needle[0]!='\0')
{
file_info_t *tmp;
struct td_list_head *pos_org=pos;
const int pos_num_org=pos_num;
tmp=td_list_entry(pos, file_info_t, list);
while(pos->next!=&dir_list->list &&
strcmp(tmp->name, needle)!=0)
{
pos=pos->next;
tmp=td_list_entry(pos, file_info_t, list);
pos_num++;
}
if(pos==&dir_list->list)
{
pos=pos_org;
pos_num=pos_num_org;
}
}
}
break;
}
if(pos_num<offset)
offset=pos_num;
if(pos_num>=offset+INTER_DIR)
offset=pos_num-INTER_DIR+1;
}
} while(quit==0 && old_LINES==LINES);
} while(quit==0);
return -1;
}
static int dir_partition_aux(disk_t *disk, const partition_t *partition, dir_data_t *dir_data, const unsigned long int inode, const unsigned int depth, char**current_cmd)
{
#define MAX_DIR_NBR 256
static unsigned long int inode_known[MAX_DIR_NBR];
if(depth==MAX_DIR_NBR)
return 1; /* subdirectories depth is too high => Back */
if(dir_data->verbose>0)
{
log_info("\ndir_partition inode=%lu\n",inode);
log_partition(disk, partition);
}
while(1)
{
const unsigned int current_directory_namelength=strlen(dir_data->current_directory);
long int new_inode=-1; /* Quit */
file_info_t dir_list = {
.list = TD_LIST_HEAD_INIT(dir_list.list),
.name = NULL
};
/* Not perfect for FAT32 root cluster */
inode_known[depth]=inode;
dir_data->get_dir(disk, partition, dir_data, inode, &dir_list);
dir_aff_log(dir_data, &dir_list);
if(*current_cmd!=NULL)
{
/* TODO: handle copy_files */
dir_data->current_directory[current_directory_namelength]='\0';
delete_list_file(&dir_list);
return -1; /* Quit */
}
new_inode=dir_aff_ncurses(disk, partition, dir_data, &dir_list, inode, depth);
if(new_inode==-1 || new_inode==1) /* -1:Quit or 1:Back */
{
delete_list_file(&dir_list);
return new_inode;
}
if(new_inode>=2)
{
unsigned int new_inode_ok=1;
unsigned int i;
for(i=0;i<=depth && new_inode_ok!=0;i++)
if((unsigned)new_inode==inode_known[i]) /* Avoid loop */
new_inode_ok=0;
if(new_inode_ok>0)
{
dir_partition_aux(disk, partition, dir_data, (unsigned long int)new_inode, depth+1, current_cmd);
}
}
/* restore current_directory name */
dir_data->current_directory[current_directory_namelength]='\0';
delete_list_file(&dir_list);
}
}
int dir_partition_aff(disk_t *disk, const partition_t *partition, dir_data_t *dir_data, const unsigned long int inode, char **current_cmd)
{
if(dir_data==NULL)
return -1;
return dir_partition_aux(disk, partition, dir_data, inode, 0, current_cmd);
}
/*
Returns
-2: no file copied
-1: failed to copy some files
0: all files has been copied
*/
#define MAX_DIR_NBR 256
static void copy_dir(WINDOW *window, disk_t *disk, const partition_t *partition, dir_data_t *dir_data, const file_info_t *dir, unsigned int *copy_ok, unsigned int *copy_bad)
{
static unsigned int dir_nbr=0;
static unsigned long int inode_known[MAX_DIR_NBR];
file_info_t dir_list = {
.list = TD_LIST_HEAD_INIT(dir_list.list),
.name = NULL
};
const unsigned int current_directory_namelength=strlen(dir_data->current_directory);
char *dir_name;
struct td_list_head *file_walker = NULL;
if(dir_data->get_dir==NULL || dir_data->copy_file==NULL)
return;
inode_known[dir_nbr++]=dir->st_ino;
dir_name=mkdir_local(dir_data->local_dir, dir_data->current_directory);
dir_data->get_dir(disk, partition, dir_data, (const unsigned long int)dir->st_ino, &dir_list);
td_list_for_each(file_walker, &dir_list.list)
{
const file_info_t *current_file;
current_file=td_list_entry(file_walker, file_info_t, list);
dir_data->current_directory[current_directory_namelength]='\0';
if(current_directory_namelength+1+strlen(current_file->name)<sizeof(dir_data->current_directory)-1)
{
if(strcmp(dir_data->current_directory,"/"))
strcat(dir_data->current_directory,"/");
strcat(dir_data->current_directory,current_file->name);
if(LINUX_S_ISDIR(current_file->st_mode)!=0)
{
const unsigned long int new_inode=current_file->st_ino;
unsigned int new_inode_ok=1;
unsigned int i;
if(new_inode<2)
new_inode_ok=0;
if(strcmp(current_file->name,"..")==0 || strcmp(current_file->name,".")==0)
new_inode_ok=0;
for(i=0;i<dir_nbr && new_inode_ok!=0;i++)
if(new_inode==inode_known[i]) /* Avoid loop */
new_inode_ok=0;
if(new_inode_ok>0)
{
copy_dir(window, disk, partition, dir_data, current_file, copy_ok, copy_bad);
}
}
else if(LINUX_S_ISREG(current_file->st_mode)!=0)
{
copy_progress(window, *copy_ok, *copy_bad);
if(dir_data->copy_file(disk, partition, dir_data, current_file)==0)
(*copy_ok)++;
else
(*copy_bad)++;
}
}
}
dir_data->current_directory[current_directory_namelength]='\0';
delete_list_file(&dir_list);
set_date(dir_name, dir->td_atime, dir->td_mtime);
free(dir_name);
dir_nbr--;
}
#endif