/* File: exfat_dir.c Copyright (C) 2011 Christophe GRENIER 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 #endif #include #include #ifdef HAVE_ICONV_H #include #endif #ifdef HAVE_STRING_H #include #endif #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_TIME_H #include #endif #ifdef HAVE_SYS_STAT_H #include #endif #include #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 exfat_dir(disk_t *disk, const partition_t *partition, dir_data_t *dir_data, const unsigned long int first_cluster, file_info_t *dir_list); static copy_file_t exfat_copy(disk_t *disk, const partition_t *partition, dir_data_t *dir_data, const file_info_t *file); static void dir_partition_exfat_close(dir_data_t *dir_data); #if 0 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; } } #endif #ifdef HAVE_ICONV static int exfat_ucstoutf8(iconv_t cd, const unsigned char *ins, const unsigned int ins_len, char **outs, const unsigned int outs_len) { const char *inp; char *outp; size_t inb_left, outb_left; if (cd == (iconv_t)(-1)) return -1; outp = *outs; inp = (const char *)ins; inb_left = ins_len; outb_left = outs_len - 1; // reserve 1 byte for NUL if (iconv(cd, (char **)&inp, &inb_left, &outp, &outb_left) == (size_t)(-1)) { // Regardless of the value of errno log_error("exfat_ucstoutf8: iconv failed %s\n", strerror(errno)); return -1; } *outp = '\0'; return 0; } #else /* * Convert a UTF-16LE text to UTF-8 * Note : wcstombs() not used because on Linux it fails for characters * not present in current locale * Returns size or zero for invalid input */ static unsigned int makeutf8(char *utf8, const char *utf16, int length) { int i; unsigned int size; unsigned int rem; enum { BASE, SURR, ERR } state; size = 0; rem = 0; state = BASE; for (i=0; i<2*length; i+=2) { switch (state) { case BASE : if (utf16[i+1] & 0xf8) { if ((utf16[i+1] & 0xf8) == 0xd8) { if (utf16[i+1] & 4) state = ERR; else { utf8[size++] = 0xf0 + (utf16[i+1] & 7) + ((utf16[i] & 0xc0) == 0xc0); utf8[size++] = 0x80 + (((utf16[i] + 64) >> 2) & 63); rem = utf16[i] & 3; state = SURR; } } else { utf8[size++] = 0xe0 + ((utf16[i+1] >> 4) & 15); utf8[size++] = 0x80 + ((utf16[i+1] & 15) << 2) + ((utf16[i] >> 6) & 3); utf8[size++] = 0x80 + (utf16[i] & 63); } } else if ((utf16[i] & 0x80) || utf16[i+1]) { utf8[size++] = 0xc0 + ((utf16[i+1] & 15) << 2) + ((utf16[i] >> 6) & 3); utf8[size++] = 0x80 + (utf16[i] & 63); } else utf8[size++] = utf16[i]; break; case SURR : if ((utf16[i+1] & 0xfc) == 0xdc) { utf8[size++] = 0x80 + (rem << 4) + ((utf16[i+1] & 3) << 2) + ((utf16[i] >> 6) & 3); utf8[size++] = 0x80 + (utf16[i] & 63); state = BASE; } else state = ERR; break; case ERR : break; } } utf8[size] = 0; if (state != BASE) state = ERR; return (state == ERR ? 0 : size); } #endif #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 */ static unsigned int exfat_get_next_cluster(disk_t *disk_car,const partition_t *partition, const uint64_t offset, const unsigned int cluster) { unsigned char *buffer=(unsigned char*)MALLOC(disk_car->sector_size); unsigned int next_cluster; const uint32_t *p32=(const uint32_t*)buffer; const uint64_t offset_s=cluster / (disk_car->sector_size/4); const uint64_t offset_o=cluster % (disk_car->sector_size/4); if((unsigned)disk_car->pread(disk_car, buffer, disk_car->sector_size, partition->part_offset + offset + offset_s * disk_car->sector_size) != disk_car->sector_size) { log_error("exfat_get_next_cluster read error\n"); free(buffer); return 0; } /* 0x00000000: free cluster * 0xFFFFFFF7: bad cluster * 0xFFFFFFFF: EOC End of cluster * */ next_cluster=le32(p32[offset_o]); free(buffer); return next_cluster; } static int dir_exfat_aux(const unsigned char*buffer, const unsigned int size, const dir_data_t *dir_data, file_info_t *dir_list) { #ifdef HAVE_ICONV const struct exfat_dir_struct *ls=(const struct exfat_dir_struct*)dir_data->private_dir_data; #endif /* * 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_info_t *current_file=NULL; unsigned int offset=0; unsigned int sec_count=0; for(offset=0; offsetparam & 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_info_t *new_file=(file_info_t *)MALLOC(sizeof(*new_file)); sec_count=entry->sec_count; new_file->name=(char *)MALLOC(512); new_file->name[0]=0; new_file->st_ino=0; new_file->st_mode = EXFAT_MKMODE(entry->attr,(LINUX_S_IRWXUGO & ~(LINUX_S_IWGRP|LINUX_S_IWOTH))); new_file->st_uid=0; new_file->st_gid=0; new_file->st_size=0; new_file->td_atime=date_dos2unix(le16(entry->atime),le16(entry->adate)); new_file->td_ctime=date_dos2unix(le16(entry->ctime),le16(entry->cdate)); new_file->td_mtime=date_dos2unix(le16(entry->mtime),le16(entry->mdate)); new_file->status=((entry->type&0x80)==0x80?0:FILE_STATUS_DELETED); current_file=new_file; td_list_add_tail(&new_file->list, &dir_list->list); } 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->st_size=le64(entry->data_length); current_file->st_ino=le32(entry->first_cluster); #if 0 if((entry->first_cluster&2)!=0) current_file->st_size=0; #endif } else if((buffer[offset]&0x7f)==0x41) { char *outs; unsigned int i; unsigned int j; for(j=0; j<255 && current_file->name[j]!='\0'; j++); for(i=2; i<32 && (buffer[offset+i]!=0 || buffer[offset+i+1]!=0); i+=2); i-=2; outs=¤t_file->name[j]; #ifdef HAVE_ICONV if(exfat_ucstoutf8(ls->cd, &buffer[offset+2], i, &outs, 512-j) < 0) { for(i=2; i<32; i+=2) current_file->name[j++]=buffer[offset+i]; current_file->name[j]='\0'; } #else makeutf8(outs, &buffer[offset+2], i); #endif } sec_count--; } } return 0; } typedef enum {exFAT_FOLLOW_CLUSTER, exFAT_NEXT_FREE_CLUSTER, exFAT_NEXT_CLUSTER} exfat_method_t; static int is_EOC(const unsigned int cluster) { return(cluster==0xFFFFFFFF); } #define NBR_CLUSTER_MAX 30 static int exfat_dir(disk_t *disk, const partition_t *partition, dir_data_t *dir_data, const unsigned long int first_cluster, file_info_t *dir_list) { 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 int cluster; unsigned char *buffer_dir=(unsigned char *)MALLOC(NBR_CLUSTER_MAX << cluster_shift); unsigned int nbr_cluster; const unsigned int total_clusters=le32(exfat_header->total_clusters); exfat_method_t exfat_meth=exFAT_FOLLOW_CLUSTER; int stop=0; const uint64_t start_exfat1=(uint64_t)le32(exfat_header->fat_blocknr) << exfat_header->blocksize_bits; if(first_cluster<2) cluster=le32(exfat_header->rootdir_clusnr); else cluster=first_cluster; memset(buffer_dir, 0, NBR_CLUSTER_MAX<=2 && nbr_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(++cluster0) dir_exfat_aux(buffer_dir, nbr_cluster<pread(disk, exfat_header, 0x200, partition->part_offset) != 0x200) { log_error("Can't read exFAT boot sector.\n"); free(exfat_header); return DIR_PART_EIO; } if(le16(exfat_header->signature)!=0xAA55 || memcmp(exfat_header->oem_id, "EXFAT ", sizeof(exfat_header->oem_id))!=0) { log_error("Not an exFAT boot sector.\n"); free(exfat_header); return DIR_PART_EIO; } 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 #ifdef DEBUG_EXFAT log_info("start_sector=%llu\n", (long long unsigned)le64(exfat_header->start_sector)); log_info("nr_sectors =%llu\n", (long long unsigned)le64(exfat_header->nr_sectors)); log_info("fat_blocknr =%u\n",le32(exfat_header->fat_blocknr)); log_info("fat_block_counts=%u\n", le32(exfat_header->fat_block_counts)); log_info("clus_blocknr=%u\n", le32(exfat_header->clus_blocknr)); log_info("total_clusters=%u\n", le32(exfat_header->total_clusters)); log_info("rootdir_clusnr=%u\n", le32(exfat_header->rootdir_clusnr)); log_info("serial_number=0x%08x\n", le32(exfat_header->serial_number)); log_info("state=0x%x\n", le16(exfat_header->state)); log_info("blocksize_bits=%u\n", exfat_header->blocksize_bits); log_info("block_per_clus_bits=%u\n", exfat_header->block_per_clus_bits); log_info("number_of_fats=%u\n", exfat_header->number_of_fats); log_info("drive_select=0x%x\n", exfat_header->drive_select); log_info("allocated_percent=%u\n", exfat_header->allocated_percent); #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 DIR_PART_OK; } 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 copy_file_t exfat_copy(disk_t *disk, const partition_t *partition, dir_data_t *dir_data, const file_info_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<st_size; exfat_method_t exfat_meth=exFAT_FOLLOW_CLUSTER; uint64_t start_exfat1; unsigned long int 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 CP_CREATE_FAILED; } cluster = file->st_ino; start_exfat1=(uint64_t)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->td_atime, file->td_mtime); free(new_file); free(buffer_file); return CP_NOSPACE; } file_size -= toread; if(file_size>0) { if(exfat_meth==exFAT_FOLLOW_CLUSTER) { const unsigned int next_cluster=exfat_get_next_cluster(disk, partition, start_exfat1, cluster); if(next_cluster>=2 && next_cluster<=total_clusters) cluster=next_cluster; else if(cluster==file->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(++clustertd_atime, file->td_mtime); free(new_file); free(buffer_file); return CP_OK; }