diff options
author | Christophe Grenier <grenier@cgsecurity.org> | 2011-03-06 11:39:23 +0100 |
---|---|---|
committer | Christophe Grenier <grenier@cgsecurity.org> | 2011-03-06 11:39:23 +0100 |
commit | fb86625be2f4f11bf856b3ac78432e5f0710be87 (patch) | |
tree | 318c3e5daf8feb5684b9bd2e7db6cf85b2b3d1f7 /src/exfat_dir.c | |
parent | 66e4280fa089d32b82b5e9c6cf8fa0b3da107319 (diff) |
Add missing src/exfat_dir.[ch] files since 2011-02-07
Diffstat (limited to 'src/exfat_dir.c')
-rw-r--r-- | src/exfat_dir.c | 420 |
1 files changed, 420 insertions, 0 deletions
diff --git a/src/exfat_dir.c b/src/exfat_dir.c new file mode 100644 index 0000000..5787cd3 --- /dev/null +++ b/src/exfat_dir.c @@ -0,0 +1,420 @@ +/* + + File: exfat_dir.c + + Copyright (C) 2011 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 +#include <stdio.h> +#include <ctype.h> +#ifdef HAVE_ICONV_H +#include <iconv.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_TIME_H +#include <time.h> +#endif +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#include "types.h" +#include "common.h" +#include "exfat.h" +#include "lang.h" +#include "intrf.h" +#include "dir.h" +#include "exfat_dir.h" +#include "log.h" +#include "setdate.h" +#include "fat.h" + +#define EXFAT_MKMODE(a,m) ((m & (a & ATTR_RO ? LINUX_S_IRUGO|LINUX_S_IXUGO : LINUX_S_IRWXUGO)) | (a & ATTR_DIR ? LINUX_S_IFDIR : LINUX_S_IFREG)) +struct exfat_dir_struct +{ + struct exfat_super_block*boot_sector; +#ifdef HAVE_ICONV + iconv_t cd; +#endif +}; + + +static int date_dos2unix(const unsigned short f_time,const unsigned short f_date); +static file_data_t *exfat_dir(disk_t *disk, const partition_t *partition, dir_data_t *dir_data, const unsigned long int first_cluster); +static inline void exfat16_towchar(wchar_t *dst, const uint8_t *src, size_t len); +static int exfat_copy(disk_t *disk, const partition_t *partition, dir_data_t *dir_data, const file_data_t *file); +static void dir_partition_exfat_close(dir_data_t *dir_data); + +static int32_t secwest; + +static inline void exfat16_towchar(wchar_t *dst, const uint8_t *src, size_t len) +{ + while (len--) { + *dst++ = src[0] | (src[1] << 8); + src += 2; + } +} + +#define ATTR_RO 1 /* read-only */ +#define ATTR_HIDDEN 2 /* hidden */ +#define ATTR_SYS 4 /* system */ +#define ATTR_DIR 16 /* directory */ +#define ATTR_ARCH 32 /* archived */ +#define EXFAT_MKMODE(a,m) ((m & (a & ATTR_RO ? LINUX_S_IRUGO|LINUX_S_IXUGO : LINUX_S_IRWXUGO)) | (a & ATTR_DIR ? LINUX_S_IFDIR : LINUX_S_IFREG)) + +file_data_t *dir_exfat_aux(const unsigned char*buffer, const unsigned int size, const unsigned int cluster_size, const unsigned int param) +{ + /* + * 0x83 Volume label + * 0x81 Allocation bitmap + * 0x82 Upcase tabel + * 0x85 File -> 0x05 + * 0xC0 Stream extension -> 0x40 + * 0xC1 File name extension -> 0x41 + * 0xA0 Volume GUID + * 0xA1 TexFAT padding + * 0xE2 Windows CE ACL + * + */ + file_data_t *dir_list=NULL; + file_data_t *current_file=NULL; + unsigned int offset=0; + unsigned int sec_count=0; + for(offset=0; offset<size; offset+=0x20) + { + if((buffer[offset]&0x80)==0 && + (param & FLAG_LIST_DELETED)!=FLAG_LIST_DELETED) + continue; + if((buffer[offset]&0x7f)==0x05) + { /* File directory entry */ + const struct exfat_file_entry *entry=(const struct exfat_file_entry *)&buffer[offset]; + file_data_t *new_file=(file_data_t *)MALLOC(sizeof(*new_file)); + sec_count=entry->sec_count; + new_file->name[0]=0; + new_file->stat.st_dev=0; + new_file->stat.st_ino=0; + new_file->stat.st_mode = EXFAT_MKMODE(entry->attr,(LINUX_S_IRWXUGO & ~(LINUX_S_IWGRP|LINUX_S_IWOTH))); + new_file->stat.st_nlink=0; + new_file->stat.st_uid=0; + new_file->stat.st_gid=0; + new_file->stat.st_rdev=0; + new_file->stat.st_size=0; +#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE + new_file->stat.st_blksize=0; +#ifdef HAVE_STRUCT_STAT_ST_BLOCKS + if(new_file->stat.st_blksize!=0) + { + new_file->stat.st_blocks=0; + } +#endif +#endif + new_file->stat.st_atime=date_dos2unix(le16(entry->atime),le16(entry->adate)); + new_file->stat.st_ctime=date_dos2unix(le16(entry->ctime),le16(entry->cdate)); + new_file->stat.st_mtime=date_dos2unix(le16(entry->mtime),le16(entry->mdate)); + new_file->status=((entry->type&0x80)==0x80?0:FILE_STATUS_DELETED); + new_file->prev=current_file; + new_file->next=NULL; + /* log_debug("exfat: new file %s de=%p size=%u\n",new_file->name,de,le32(de->size)); */ + if(current_file!=NULL) + current_file->next=new_file; + else + dir_list=new_file; + current_file=new_file; + } + else if(sec_count>0 && current_file!=NULL) + { + if((buffer[offset]&0x7f)==0x40) + { + /* Stream extension */ + const struct exfat_stream_ext_entry *entry=(const struct exfat_stream_ext_entry*)&buffer[offset]; + current_file->stat.st_size=le64(entry->data_length); + current_file->stat.st_ino=le32(entry->first_cluster); +#if 0 + if((entry->first_cluster&2)!=0) + current_file->stat.st_size=0; +#endif + } + else if((buffer[offset]&0x7f)==0x41) + { + unsigned int i,j; + for(j=0; j<255 && current_file->name[j]!='\0'; j++); + /* FIXME see ntfs_ucstoutf8 && ntfs_ucstombs*/ + for(i=2; i<32; i+=2) + current_file->name[j++]=buffer[offset+i]; + current_file->name[j]='\0'; + } + sec_count--; + } + } + return dir_list; +} + +static int day_n[] = { 0,31,59,90,120,151,181,212,243,273,304,334,0,0,0,0 }; + /* JanFebMarApr May Jun Jul Aug Sep Oct Nov Dec */ + +/* Convert a MS-DOS time/date pair to a UNIX date (seconds since 1 1 70). */ + +static int date_dos2unix(const unsigned short f_time, const unsigned short f_date) +{ + int month,year,secs; + + /* first subtract and mask after that... Otherwise, if + f_date == 0, bad things happen */ + month = ((f_date >> 5) - 1) & 15; + year = f_date >> 9; + secs = (f_time & 31)*2+60*((f_time >> 5) & 63)+(f_time >> 11)*3600+86400* + ((f_date & 31)-1+day_n[month]+(year/4)+year*365-((year & 3) == 0 && + month < 2 ? 1 : 0)+3653); + /* days since 1.1.70 plus 80's leap day */ + return secs+secwest; +} + +enum {exFAT_FOLLOW_CLUSTER, exFAT_NEXT_FREE_CLUSTER, exFAT_NEXT_CLUSTER}; + +static int is_EOC(const unsigned int cluster) +{ + return(cluster==0xFFFFFFFF); +} + +#define NBR_CLUSTER_MAX 30 +static file_data_t *exfat_dir(disk_t *disk, const partition_t *partition, dir_data_t *dir_data, const unsigned long int first_cluster) +{ + const struct exfat_dir_struct *ls=(const struct exfat_dir_struct*)dir_data->private_dir_data; + const struct exfat_super_block*exfat_header=ls->boot_sector; + const unsigned int cluster_shift=exfat_header->block_per_clus_bits + exfat_header->blocksize_bits; + file_data_t *dir_list=NULL; + unsigned int cluster; + unsigned char *buffer_dir=(unsigned char *)MALLOC(NBR_CLUSTER_MAX << cluster_shift); + unsigned int nbr_cluster; + unsigned int clus_blocknr; + unsigned int total_clusters; + unsigned int exfat_meth=exFAT_FOLLOW_CLUSTER; + int stop=0; + if(first_cluster<2) + cluster=le32(exfat_header->rootdir_clusnr); + else + cluster=first_cluster; + memset(buffer_dir, 0, NBR_CLUSTER_MAX<<cluster_shift); + clus_blocknr=le32(exfat_header->clus_blocknr); + total_clusters=le32(exfat_header->total_clusters); + nbr_cluster=0; + while(!is_EOC(cluster) && cluster>=2 && nbr_cluster<NBR_CLUSTER_MAX && stop==0) + { + if(exfat_read_cluster(disk, partition, exfat_header, buffer_dir + (uint64_t) (nbr_cluster<< cluster_shift), cluster) != (1<<cluster_shift)) + { + log_error("exFAT: Can't read directory cluster.\n"); + stop=1; + } + if(stop==0 && nbr_cluster==0 && first_cluster!=0 && + !(buffer_dir[0]=='.' && buffer_dir[0x20]=='.' && buffer_dir[0x21]=='.')) + { + stop=1; + } + if(stop==0) + { + if(exfat_meth==exFAT_FOLLOW_CLUSTER) + { +// const unsigned int next_cluster=get_next_cluster(disk, partition, partition->upart_type, start_exfat1, cluster); + const unsigned int next_cluster=0; + if((next_cluster>=2 && next_cluster<=total_clusters) || + is_EOC(next_cluster)) + cluster=next_cluster; + else if(next_cluster==0) + { +#if 0 + /* FIXME: experimental */ + if(cluster==first_cluster && (dir_data->param & FLAG_LIST_DELETED)==FLAG_LIST_DELETED) + exfat_meth=exFAT_NEXT_FREE_CLUSTER; /* Recovery of a deleted directory */ + else + cluster=0; /* Stop directory listing */ +#else + cluster=0; /* Stop directory listing */ +#endif + } + else + exfat_meth=exFAT_NEXT_CLUSTER; /* exFAT is corrupted, don't trust it */ + } + if(exfat_meth==exFAT_NEXT_CLUSTER) + cluster++; + else if(exfat_meth==exFAT_NEXT_FREE_CLUSTER) + { /* Deleted directories are composed of "free" clusters */ +#if 0 + while(++cluster<total_clusters && + get_next_cluster(disk, partition, partition->upart_type, start_exfat1, cluster)!=0); +#endif + } + nbr_cluster++; + } + } + if(nbr_cluster>0) + dir_list=dir_exfat_aux(buffer_dir, nbr_cluster<<cluster_shift, 1<<cluster_shift, dir_data->param); + free(buffer_dir); + return dir_list; +} + +static void set_secwest(void) +{ + struct tm *tmptr; + time_t t; + + t = time(NULL); + tmptr = localtime(&t); +#ifdef HAVE_STRUCT_TM_TM_GMTOFF + secwest = -1 * tmptr->tm_gmtoff; +#elif defined (DJGPP) + secwest = 0; +#else +#if defined (__CYGWIN__) + secwest = _timezone; +#else + secwest = timezone; +#endif + /* account for daylight savings */ + if (tmptr->tm_isdst) + secwest -= 3600; +#endif +} + +int dir_partition_exfat_init(disk_t *disk, const partition_t *partition, dir_data_t *dir_data, const int verbose) +{ + static struct exfat_dir_struct *ls; + struct exfat_super_block *exfat_header; + set_secwest(); + /* Load boot sector */ + exfat_header=(struct exfat_super_block *)MALLOC(0x200); + if(disk->pread(disk, exfat_header, 0x200, partition->part_offset) != 0x200) + { + log_error("Can't read exFAT boot sector.\n"); + free(exfat_header); + return -1; + } + ls=(struct exfat_dir_struct *)MALLOC(sizeof(*ls)); + ls->boot_sector=exfat_header; +#ifdef HAVE_ICONV + if ((ls->cd = iconv_open("UTF-8", "UTF-16LE")) == (iconv_t)(-1)) + { + log_error("dir_partition_exfat_init: iconv_open failed\n"); + } +#endif + strncpy(dir_data->current_directory,"/",sizeof(dir_data->current_directory)); + dir_data->current_inode=0; + dir_data->param=FLAG_LIST_DELETED; + dir_data->verbose=verbose; + dir_data->capabilities=CAPA_LIST_DELETED; + dir_data->copy_file=exfat_copy; + dir_data->close=dir_partition_exfat_close; + dir_data->local_dir=NULL; + dir_data->private_dir_data=ls; + dir_data->get_dir=exfat_dir; + return 0; +} + +static void dir_partition_exfat_close(dir_data_t *dir_data) +{ + struct exfat_dir_struct *ls=(struct exfat_dir_struct*)dir_data->private_dir_data; + free(ls->boot_sector); +#ifdef HAVE_ICONV + if (ls->cd != (iconv_t)(-1)) + iconv_close(ls->cd); +#endif + free(ls); +} + +static int exfat_copy(disk_t *disk, const partition_t *partition, dir_data_t *dir_data, const file_data_t *file) +{ + char *new_file; + FILE *f_out; + const struct exfat_dir_struct *ls=(const struct exfat_dir_struct*)dir_data->private_dir_data; + const struct exfat_super_block *exfat_header=ls->boot_sector; + const unsigned int cluster_shift=exfat_header->block_per_clus_bits + exfat_header->blocksize_bits; + unsigned char *buffer_file=(unsigned char *)MALLOC(1<<cluster_shift); + unsigned int cluster; + unsigned int file_size=file->stat.st_size; + unsigned int exfat_meth=exFAT_FOLLOW_CLUSTER; + uint64_t start_exfat1,clus_blocknr; + unsigned long int total_clusters; + f_out=fopen_local(&new_file, dir_data->local_dir, dir_data->current_directory); + if(!f_out) + { + log_critical("Can't create file %s: \n",new_file); + free(new_file); + free(buffer_file); + return -1; + } + cluster = file->stat.st_ino; + start_exfat1=le32(exfat_header->fat_blocknr) << exfat_header->blocksize_bits; + clus_blocknr=le32(exfat_header->clus_blocknr); + total_clusters=le32(exfat_header->total_clusters); + log_trace("exfat_copy dst=%s first_cluster=%u (%llu) size=%lu\n", new_file, + cluster, + (long long unsigned)(((cluster-2) << exfat_header->block_per_clus_bits) + clus_blocknr), + (long unsigned)file_size); + + while(cluster>=2 && cluster<=total_clusters && file_size>0) + { + unsigned int toread = 1 << cluster_shift; + if (toread > file_size) + toread = file_size; + if((unsigned)exfat_read_cluster(disk, partition, exfat_header, buffer_file, cluster) != toread) + { + log_error("exfat_copy: Can't read cluster %u.\n", cluster); + } + if(fwrite(buffer_file, 1, toread, f_out) != toread) + { + log_error("exfat_copy: no space left on destination.\n"); + fclose(f_out); + set_date(new_file, file->stat.st_atime, file->stat.st_mtime); + free(new_file); + free(buffer_file); + return -1; + } + file_size -= toread; + if(file_size>0) + { + if(exfat_meth==exFAT_FOLLOW_CLUSTER) + { + const unsigned int next_cluster=get_next_cluster(disk, partition, UP_FAT32, start_exfat1, cluster); + if(next_cluster>=2 && next_cluster<=total_clusters) + cluster=next_cluster; + else if(cluster==file->stat.st_ino && next_cluster==0) + exfat_meth=exFAT_NEXT_FREE_CLUSTER; /* Recovery of a deleted file */ + else + exfat_meth=exFAT_NEXT_CLUSTER; /* exFAT is corrupted, don't trust it */ + } + if(exfat_meth==exFAT_NEXT_CLUSTER) + cluster++; + else if(exfat_meth==exFAT_NEXT_FREE_CLUSTER) + { /* Deleted file are composed of "free" clusters */ + while(++cluster<total_clusters && + get_next_cluster(disk, partition, partition->upart_type, start_exfat1, cluster)!=0); + } + } + } + fclose(f_out); + set_date(new_file, file->stat.st_atime, file->stat.st_mtime); + free(new_file); + free(buffer_file); + return 0; +} |