From 4297404391e6d828d71306f7402ba1cdd05297aa Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Fri, 7 May 2004 01:45:49 +0000 Subject: - Fixed attribute List problems. - Super fragmented MFTs work - Ignore compressed and encrypted files - Simplified memory management. - Skip simple files - Win32 compatibility work --- config.win32.h | 2 + src/compat.c | 16 +- src/compat.h | 6 +- src/main.c | 23 ++- src/misc.c | 35 ++++- src/ntfs.c | 4 +- src/ntfs.h | 4 +- src/ntfsx.c | 480 ++++++++++++++++++++++++++++++++++++++------------------- src/ntfsx.h | 21 ++- src/scrounge.c | 416 ++++++++++++++++++++++++++++++------------------- src/scrounge.h | 6 + src/unicode.c | 6 +- 12 files changed, 673 insertions(+), 346 deletions(-) diff --git a/config.win32.h b/config.win32.h index 497abfb..4d1f34c 100644 --- a/config.win32.h +++ b/config.win32.h @@ -200,3 +200,5 @@ /* Define to `unsigned' if does not define. */ /* #undef size_t */ + +#define SIZEOF_WHCHAR_T 2 \ No newline at end of file diff --git a/src/compat.c b/src/compat.c index 013675f..fa171f1 100644 --- a/src/compat.c +++ b/src/compat.c @@ -288,11 +288,21 @@ void vwarnx(const char *fmt, va_list ap) void* reallocf(void* ptr, size_t size) { void* ret = realloc(ptr, size); + if(!ret) + errx(1, "out of memory"); + return ret; +} + +#endif - if(!ret && size) - free(ptr); +#ifndef HAVE_MALLOCF - return ret; +void* mallocf(size_t size) +{ + void* ret = malloc(size); + if(!ret) + errx(1, "out of memory"); + return ret; } #endif diff --git a/src/compat.h b/src/compat.h index c2b4128..65a0c8e 100644 --- a/src/compat.h +++ b/src/compat.h @@ -192,6 +192,10 @@ void vwarnx(const char *fmt, va_list ap); void* reallocf(void* p, size_t sz); #endif +#ifndef HAVE_MALLOCF +void* mallocf(size_t sz); +#endif + /* Some number conversion stuff */ #include @@ -223,7 +227,7 @@ void* reallocf(void* p, size_t sz); #ifdef _WIN32 - #ifdef SIZEOF_WHCHAR_T != 2 + #if SIZEOF_WHCHAR_T != 2 #error Incompatible size of wchar_t #endif diff --git a/src/main.c b/src/main.c index ef5be8e..ca8aa79 100644 --- a/src/main.c +++ b/src/main.c @@ -70,6 +70,10 @@ usage: scrounge [-m mftoffset] [-c clustersize] [-o outdir] disk start end \n\ /* Forward decls */ void usage(); +#ifdef _DEBUG +bool g_verifyMode = false; +#endif + int main(int argc, char* argv[]) { int ch = 0; @@ -88,9 +92,9 @@ int main(int argc, char* argv[]) pi.cluster = 8; #ifdef _WIN32 - while((ch = getopt(argc, argv, "c:d:hlm:o:s")) != -1) + while((ch = getopt(argc, argv, "c:d:hlm:o:sv")) != -1) #else - while((ch = getopt(argc, argv, "c:hlm:o:s")) != -1) + while((ch = getopt(argc, argv, "c:hlm:o:sv")) != -1) #endif { switch(ch) @@ -125,12 +129,6 @@ int main(int argc, char* argv[]) break; #endif - /* help mode */ - case '?': - case 'h': - usage(); - break; - /* list mode */ case 'l': { @@ -171,6 +169,15 @@ int main(int argc, char* argv[]) } break; +#ifdef _DEBUG + case 'v': + g_verifyMode = true; + break; +#endif + + /* help mode */ + case '?': + case 'h': default: usage(); break; diff --git a/src/misc.c b/src/misc.c index 0778467..686e460 100644 --- a/src/misc.c +++ b/src/misc.c @@ -38,9 +38,6 @@ void addLocationLock(drivelocks* locks, uint64 beg, uint64 end) { locks->_count += 0x400; locks->_locks = (struct drivelock*)reallocf(locks->_locks, sizeof(struct drivelock) * locks->_count); - - if(!locks->_locks) - errx(1, "out of memory"); } /* TODO: Implement a more efficient method here! */ @@ -110,7 +107,7 @@ const size_t kRefSig = 0x1F2F3F4F; void* _refalloc_dbg(size_t sz) { /* Allocate extra counter value before memory */ - size_t* mem = (size_t*)malloc(sz * sizeof(size_t) * 2); + size_t* mem = (size_t*)mallocf(sz * sizeof(size_t) * 2); if(mem) { @@ -126,7 +123,7 @@ void* _refalloc_dbg(size_t sz) void* _refalloc(size_t sz) { /* Allocate extra counter value before memory */ - size_t* mem = (size_t*)malloc(sz * sizeof(size_t) * 1); + size_t* mem = (size_t*)mallocf(sz * sizeof(size_t) * 1); if(mem) { @@ -188,4 +185,32 @@ void _refrelease(void* buf) } } +#define COMPARE_BLOCK_SIZE 4096 +int compareFileData(int f, void* data, size_t length) +{ + unsigned char buf[COMPARE_BLOCK_SIZE]; + unsigned char* d = (unsigned char*)data; + int num, r; + + while(length > 0) + { + num = min(COMPARE_BLOCK_SIZE, length); + r = read(f, buf, num); + + if(r < 0) + err(1, "error reading comparison file"); + + if(r < num) + return -1; + + r = memcmp(d, buf, num); + if(r != 0) + return r; + + d += num; + length -= num; + } + + return 0; +} diff --git a/src/ntfs.c b/src/ntfs.c index 88bab7e..3e2b657 100644 --- a/src/ntfs.c +++ b/src/ntfs.c @@ -46,7 +46,7 @@ ntfs_attribheader* ntfs_searchattribute(byte* location, uint32 attrType, byte* e return NULL; } -byte* ntfs_getattributelist(ntfs_recordheader* record) +byte* ntfs_getattributeheaders(ntfs_recordheader* record) { byte* location = (byte*)record; ASSERT(record->offAttrs != 0); @@ -57,7 +57,7 @@ byte* ntfs_getattributelist(ntfs_recordheader* record) ntfs_attribheader* ntfs_findattribute(ntfs_recordheader* record, uint32 attrType, byte* end) { - byte* location = ntfs_getattributelist(record); + byte* location = ntfs_getattributeheaders(record); return ntfs_searchattribute(location, attrType, end, false); } diff --git a/src/ntfs.h b/src/ntfs.h index ae7ea6e..62a92cb 100644 --- a/src/ntfs.h +++ b/src/ntfs.h @@ -158,8 +158,10 @@ ntfs_attribnonresident; #ifdef FC_WIDE #define kNTFS_MFTName L"$MFT" +#define kNTFS_SysPrefix L'$' #else #define kNTFS_MFTName "$MFT" +#define kNTFS_SysPrefix '$' #endif typedef struct _ntfs_attribfilename @@ -196,7 +198,7 @@ ntfs_attriblistrecord; ntfs_attribheader* ntfs_findattribute(ntfs_recordheader* record, uint32 attrType, byte* end); ntfs_attribheader* ntfs_nextattribute(ntfs_attribheader* attrib, uint32 attrType, byte* end); -byte* ntfs_getattributelist(ntfs_recordheader* record); +byte* ntfs_getattributeheaders(ntfs_recordheader* record); byte* ntfs_getattributedata(ntfs_attribresident* attrib, byte* end); bool ntfs_isbetternamespace(byte n1, byte n2); diff --git a/src/ntfsx.c b/src/ntfsx.c index fd60f50..60ddb66 100644 --- a/src/ntfsx.c +++ b/src/ntfsx.c @@ -24,18 +24,16 @@ ntfsx_datarun* ntfsx_datarun_alloc(byte* mem, byte* datarun) { - ntfsx_datarun* dr = (ntfsx_datarun*)malloc(sizeof(ntfsx_datarun)); - if(dr) - { - ASSERT(datarun); - dr->_mem = (byte*)refadd(mem); - dr->_datarun = datarun; - dr->_curpos = NULL; - - dr->cluster = 0; - dr->length = 0; - dr->sparse = false; - } + ntfsx_datarun* dr = (ntfsx_datarun*)mallocf(sizeof(ntfsx_datarun)); + + ASSERT(datarun); + dr->_mem = (byte*)refadd(mem); + dr->_datarun = datarun; + dr->_curpos = NULL; + + dr->cluster = 0; + dr->length = 0; + dr->sparse = false; return dr; } @@ -116,20 +114,13 @@ bool ntfsx_datarun_next(ntfsx_datarun* dr) -bool ntfsx_cluster_reserve(ntfsx_cluster* clus, partitioninfo* info) +void ntfsx_cluster_reserve(ntfsx_cluster* clus, partitioninfo* info) { ntfsx_cluster_release(clus); clus->size = CLUSTER_SIZE(*info); ASSERT(clus->size != 0); clus->data = (byte*)refalloc(clus->size); - if(!clus->data) - { - errno = ENOMEM; - return false; - } - - return true; } bool ntfsx_cluster_read(ntfsx_cluster* clus, partitioninfo* info, uint64 begSector, int dd) @@ -138,10 +129,7 @@ bool ntfsx_cluster_read(ntfsx_cluster* clus, partitioninfo* info, uint64 begSect size_t sz; if(!clus->data) - { - if(!ntfsx_cluster_reserve(clus, info)) - return false; - } + ntfsx_cluster_reserve(clus, info); pos = SECTOR_TO_BYTES(begSector); if(lseek64(dd, pos, SEEK_SET) == -1) @@ -174,14 +162,10 @@ void ntfsx_cluster_release(ntfsx_cluster* clus) ntfsx_attribute* ntfsx_attribute_alloc(ntfsx_cluster* clus, ntfs_attribheader* header) { - ntfsx_attribute* attr = (ntfsx_attribute*)malloc(sizeof(ntfsx_attribute)); - if(attr) - { - attr->_header = header; - attr->_mem = (byte*)refadd(clus->data); - attr->_length = clus->size; - } - + ntfsx_attribute* attr = (ntfsx_attribute*)mallocf(sizeof(ntfsx_attribute)); + attr->_header = header; + attr->_mem = (byte*)refadd(clus->data); + attr->_length = clus->size; return attr; } @@ -236,15 +220,226 @@ bool ntfsx_attribute_next(ntfsx_attribute* attr, uint32 attrType) } -ntfsx_record* ntfsx_record_alloc(partitioninfo* info) + +#define ATTR_ENUM_LISTPRI 1 << 1 +#define ATTR_ENUM_DONEINLINE 1 << 2 +#define ATTR_ENUM_DONELIST 1 << 3 +#define ATTR_ENUM_FOUNDLIST 1 << 4 + +ntfsx_attrib_enum* ntfsx_attrib_enum_alloc(uint32 type, bool normal) { - ntfsx_record* rec = (ntfsx_record*)malloc(sizeof(ntfsx_record)); - if(rec) + ntfsx_attrib_enum* attrenum = (ntfsx_attrib_enum*)mallocf(sizeof(ntfsx_attrib_enum)); + attrenum->type = type; + attrenum->_attrhead = NULL; + attrenum->_listrec = NULL; + attrenum->_flags = normal ? ATTR_ENUM_LISTPRI : 0; + return attrenum; +} + +ntfsx_attribute* ntfsx_attrib_enum_inline(ntfsx_attrib_enum* attrenum, ntfsx_record* record) +{ + ntfsx_attribute* attr; + ntfsx_cluster* cluster; + ntfs_recordheader* rechead; + + /* If we're done */ + if(attrenum->_flags & ATTR_ENUM_DONEINLINE) + return NULL; + + cluster = ntfsx_record_cluster(record); + rechead = ntfsx_record_header(record); + + /* If this is the first time */ + if(!attrenum->_attrhead && !attrenum->_listrec) { - rec->info = info; - memset(&(rec->_clus), 0, sizeof(ntfsx_cluster)); + attrenum->_attrhead = ntfs_findattribute(rechead, attrenum->type, + cluster->data + cluster->size); + + if(attrenum->_attrhead) + { + attr = ntfsx_attribute_alloc(cluster, attrenum->_attrhead); + return attr; + } + + /* Otherwise we fall through to the attr list stuff below */ } + /* Look for another attribute in the record */ + if(attrenum->_attrhead && attrenum->_attrhead->type == attrenum->type) + { + attrenum->_attrhead = ntfs_nextattribute(attrenum->_attrhead, attrenum->type, + cluster->data + cluster->size); + + if(attrenum->_attrhead) + { + attr = ntfsx_attribute_alloc(cluster, attrenum->_attrhead); + return attr; + } + + /* Otherwise we're done */ + } + + attrenum->_flags |= ATTR_ENUM_DONEINLINE; + return NULL; +} + +ntfsx_attribute* ntfsx_attrib_enum_list(ntfsx_attrib_enum* attrenum, ntfsx_record* record) +{ + ntfsx_cluster* cluster; + ntfs_recordheader* rechead; + ntfs_attribresident* resident; + ntfs_attribheader* attrhead; + ntfsx_attribute* attr; + uint64 mftRecord; + ntfsx_record* r2; + ntfsx_cluster* c2; + + ASSERT(record && attrenum); + + /* If we're done */ + if(attrenum->_flags & ATTR_ENUM_DONELIST) + return NULL; + + cluster = ntfsx_record_cluster(record); + rechead = ntfsx_record_header(record); + + /* Okay first check for attribute lists */ + if(!attrenum->_listrec && !attrenum->_attrhead) + { + attrenum->_attrhead = ntfs_findattribute(rechead, kNTFS_ATTRIBUTE_LIST, + cluster->data + cluster->size); + + /* If no attribute list, end of story */ + if(!attrenum->_attrhead) + { + attrenum->_flags |= ATTR_ENUM_DONELIST; + return NULL; + } + + /* We don't support non-resident attribute lists (which are stupid!) */ + if(attrenum->_attrhead->bNonResident) + { + warnx("brain dead, incredibly fragmented file data. skipping"); + attrenum->_flags |= ATTR_ENUM_DONELIST; + return NULL; + } + } + + /* This has to be set by now */ + ASSERT(record->info->mftmap); + ASSERT(attrenum->_attrhead); + ASSERT(attrenum->_attrhead->type == kNTFS_ATTRIBUTE_LIST); + + resident = (ntfs_attribresident*)attrenum->_attrhead; + + for(;;) + { + if(attrenum->_listrec) /* progress to next record */ + attrenum->_listrec = (ntfs_attriblistrecord*)(((byte*)attrenum->_listrec) + attrenum->_listrec->cbRecord); + + else /* get first record */ + attrenum->_listrec = (ntfs_attriblistrecord*)((byte*)resident + resident->offAttribData); + + if(((byte*)attrenum->_listrec) >= ((byte*)attrenum->_attrhead) + attrenum->_attrhead->cbAttribute) + { + attrenum->_listrec = NULL; + attrenum->_flags |= ATTR_ENUM_DONELIST; + return NULL; + } + + if(attrenum->_listrec->type == attrenum->type) + { + attr = NULL; + r2 = NULL; + + /* Read in appropriate cluster */ + mftRecord = ntfsx_mftmap_sectorforindex(record->info->mftmap, attrenum->_listrec->refAttrib & kNTFS_RefMask); + if(mftRecord == kInvalidSector) + { + warnx("invalid sector in mft map. screwed up file. skipping data"); + } + else + { + r2 = ntfsx_record_alloc(record->info); + + if(ntfsx_record_read(r2, mftRecord, record->info->device)) + { + rechead = ntfsx_record_header(r2); + c2 = ntfsx_record_cluster(r2); + attrhead = ntfs_findattribute(rechead, attrenum->type, + c2->data + c2->size); + + if(attrhead) + attr = ntfsx_attribute_alloc(c2, attrhead); + } + } + + if(r2) + ntfsx_record_free(r2); + + if(attr) + return attr; + } + } + + /* Not reached */ + ASSERT(false); +} + +ntfsx_attribute* ntfsx_attrib_enum_all(ntfsx_attrib_enum* attrenum, ntfsx_record* record) +{ + ntfsx_attribute* attr = NULL; + + ASSERT(record && attrenum); + + /* + * When in this mode list attributes completely override + * any inline attributes. This is the normal mode of + * operation + */ + if(attrenum->_flags & ATTR_ENUM_LISTPRI) + { + if(!(attrenum->_flags & ATTR_ENUM_DONELIST)) + { + attr = ntfsx_attrib_enum_list(attrenum, record); + + if(attr) + attrenum->_flags |= ATTR_ENUM_FOUNDLIST; + } + + if(!attr && !(attrenum->_flags & ATTR_ENUM_FOUNDLIST) && + !(attrenum->_flags & ATTR_ENUM_DONEINLINE)) + attr = ntfsx_attrib_enum_inline(attrenum, record); + } + + /* + * The other mode of operation is to find everything + * inline first and then stuff in the lists. + */ + else + { + if(!(attrenum->_flags & ATTR_ENUM_DONEINLINE)) + attr = ntfsx_attrib_enum_inline(attrenum, record); + + if(!attr && !(attrenum->_flags & ATTR_ENUM_DONELIST)) + attr = ntfsx_attrib_enum_list(attrenum, record); + } + + return attr; +} + +void ntfsx_attrib_enum_free(ntfsx_attrib_enum* attrenum) +{ + free(attrenum); +} + + + +ntfsx_record* ntfsx_record_alloc(partitioninfo* info) +{ + ntfsx_record* rec = (ntfsx_record*)mallocf(sizeof(ntfsx_record)); + rec->info = info; + memset(&(rec->_clus), 0, sizeof(ntfsx_cluster)); return rec; } @@ -274,6 +469,11 @@ bool ntfsx_record_read(ntfsx_record* record, uint64 begSector, int dd) return true; } +ntfsx_cluster* ntfsx_record_cluster(ntfsx_record* record) +{ + return &(record->_clus); +} + ntfs_recordheader* ntfsx_record_header(ntfsx_record* record) { return (ntfs_recordheader*)(record->_clus.data); @@ -281,62 +481,13 @@ ntfs_recordheader* ntfsx_record_header(ntfsx_record* record) ntfsx_attribute* ntfsx_record_findattribute(ntfsx_record* record, uint32 attrType, int dd) { + ntfsx_attrib_enum* attrenum = NULL; ntfsx_attribute* attr = NULL; - ntfs_attribheader* attrhead; - ntfs_attriblistrecord* atlr; - ntfs_attribresident* resident; - uint64 mftRecord; - ntfsx_record* r2; - - /* Make sure we have a valid record */ - ASSERT(ntfsx_record_header(record)); - attrhead = ntfs_findattribute(ntfsx_record_header(record), - attrType, (record->_clus.data) + (record->_clus.size)); - if(attrhead) - { - attr = ntfsx_attribute_alloc(&(record->_clus), attrhead); - } - else - { - /* Do attribute list thing here! */ - attrhead = ntfs_findattribute(ntfsx_record_header(record), kNTFS_ATTRIBUTE_LIST, - (record->_clus.data) + (record->_clus.size)); - - /* For now we only support Resident Attribute lists */ - if(dd && attrhead && !attrhead->bNonResident && record->info->mftmap) - { - resident = (ntfs_attribresident*)attrhead; - atlr = (ntfs_attriblistrecord*)((byte*)attrhead + resident->offAttribData); - - /* Go through AttrList records looking for this attribute */ - while((byte*)atlr < (byte*)attrhead + attrhead->cbAttribute) - { - /* Found it! */ - if(atlr->type == attrType) - { - /* Read in appropriate cluster */ - mftRecord = ntfsx_mftmap_sectorforindex(record->info->mftmap, atlr->refAttrib & kNTFS_RefMask); - - r2 = ntfsx_record_alloc(record->info); - if(!r2) - return NULL; - - if(ntfsx_record_read(r2, mftRecord, dd)) - attr = ntfsx_record_findattribute(r2, attrType, dd); - - ntfsx_record_free(r2); - - if(attr) - break; - } - - atlr = (ntfs_attriblistrecord*)((byte*)atlr + atlr->cbRecord); - } - } - } - - return attr; + attrenum = ntfsx_attrib_enum_alloc(attrType, true); + attr = ntfsx_attrib_enum_all(attrenum, record); + ntfsx_attrib_enum_free(attrenum); + return attr; } @@ -372,8 +523,6 @@ static void mftmap_expand(ntfsx_mftmap* map, uint32* allocated) (*allocated) += 16; map->_blocks = (struct _ntfsx_mftmap_block*)reallocf(map->_blocks, (*allocated) * sizeof(struct _ntfsx_mftmap_block)); - if(!(map->_blocks)) - errx(1, "out of memory"); } } @@ -382,6 +531,7 @@ bool ntfsx_mftmap_load(ntfsx_mftmap* map, ntfsx_record* record, int dd) bool ret = true; ntfsx_attribute* attribdata = NULL; /* Data Attribute */ ntfsx_datarun* datarun = NULL; /* Data runs for nonresident data */ + ntfsx_attrib_enum* attrenum = NULL; { ntfs_attribheader* header; @@ -390,24 +540,8 @@ bool ntfsx_mftmap_load(ntfsx_mftmap* map, ntfsx_record* record, int dd) uint64 firstSector; uint32 allocated; uint64 total; + bool hasdata = false; - /* TODO: Check here whether MFT has already been loaded */ - - /* Get the MFT's data */ - attribdata = ntfsx_record_findattribute(record, kNTFS_DATA, dd); - if(!attribdata) - RETWARNBX("invalid mft. no data attribute"); - - header = ntfsx_attribute_header(attribdata); - if(!header->bNonResident) - RETWARNBX("invalid mft. data attribute non-resident"); - - datarun = ntfsx_attribute_getdatarun(attribdata); - if(!datarun) - RETWARNBX("invalid mft. no data runs"); - - nonres = (ntfs_attribnonresident*)header; - if(map->_blocks) { free(map->_blocks); @@ -416,65 +550,87 @@ bool ntfsx_mftmap_load(ntfsx_mftmap* map, ntfsx_record* record, int dd) map->_count = 0; allocated = 0; - mftmap_expand(map, &allocated); - - total = nonres->cbAttribData / kSectorSize; - - /* Now loop through the data run */ - if(ntfsx_datarun_first(datarun)) - { - do - { - if(datarun->sparse) - RETWARNBX("invalid mft. sparse data runs"); - - mftmap_expand(map, &allocated); - - ASSERT(map->info->cluster != 0); - - length = datarun->length * ((map->info->cluster * kSectorSize) / kNTFS_RecordLen); - if(length == 0) - continue; - - firstSector = (datarun->cluster * map->info->cluster) + map->info->first; - if(firstSector >= map->info->end) - continue; - - map->_blocks[map->_count].length = length; - map->_blocks[map->_count].firstSector = firstSector; - map->_count++; - - /* - * In some cases the data runs for the MFT don't specify the entire - * MFT file, and so we track the remainder and tack it onto - * the last block. - */ - total -= length; - } - while(ntfsx_datarun_next(datarun)); - - } + total = 0; + + attrenum = ntfsx_attrib_enum_alloc(kNTFS_DATA, false); - if(total > 0) + while((attribdata = ntfsx_attrib_enum_all(attrenum, record)) != NULL) { - if(map->_count == 0) + header = ntfsx_attribute_header(attribdata); + if(!header->bNonResident) { - /* - * When no data runs were found we start off right - * at the MFT and go for the specified length. - */ - ASSERT(allocated > 0); - map->_blocks[0].length = total; - map->_blocks[0].firstSector = map->info->mft + map->info->first; + warnx("invalid mft. data attribute non-resident"); } - else { - /* Add the remainder of the missing blocks here */ - map->_blocks[map->_count - 1].length += total; - } + datarun = ntfsx_attribute_getdatarun(attribdata); + if(!datarun) + { + warnx("invalid mft. no data runs in data attribute"); + } + else + { + hasdata = true; + nonres = (ntfs_attribnonresident*)header; + + /* Check total length against nonres->cbAllocated */ + if(map->_count == 0) + total = nonres->cbAllocated; + + /* Now loop through the data run */ + if(ntfsx_datarun_first(datarun)) + { + do + { + if(datarun->sparse) + { + warnx("invalid mft. sparse data runs"); + } + else + { + mftmap_expand(map, &allocated); + + ASSERT(map->info->cluster != 0); + + length = datarun->length * ((map->info->cluster * kSectorSize) / kNTFS_RecordLen); + if(length == 0) + continue; + + firstSector = (datarun->cluster * map->info->cluster) + map->info->first; + if(firstSector >= map->info->end) + continue; + + /* + * When the same as the last one skip. This occurs in really + * fragmented MFTs where we read the inline DATA attribute first + * and then move on to the ATTRLIST one. + */ + if(map->_count > 0 && map->_blocks[map->_count - 1].length == length && + map->_blocks[map->_count - 1].firstSector == firstSector) + continue; + + map->_blocks[map->_count].length = length; + map->_blocks[map->_count].firstSector = firstSector; + map->_count++; + + total -= length * kSectorSize; + } + } + while(ntfsx_datarun_next(datarun)); + } + + ntfsx_datarun_free(datarun); + datarun = NULL; + } + } + + ntfsx_attribute_free(attribdata); + attribdata = NULL; } + if(!hasdata) + RETWARNBX("invalid mft. no data attribute"); + ret = true; } @@ -484,6 +640,8 @@ cleanup: ntfsx_attribute_free(attribdata); if(datarun) ntfsx_datarun_free(datarun); + if(attrenum) + ntfsx_attrib_enum_free(attrenum); return ret; } diff --git a/src/ntfsx.h b/src/ntfsx.h index 04ebabd..2c1054c 100644 --- a/src/ntfsx.h +++ b/src/ntfsx.h @@ -52,7 +52,7 @@ typedef struct _ntfsx_cluster } ntfsx_cluster; -bool ntfsx_cluster_reserve(ntfsx_cluster* clus, partitioninfo* info); +void ntfsx_cluster_reserve(ntfsx_cluster* clus, partitioninfo* info); bool ntfsx_cluster_read(ntfsx_cluster* clus, partitioninfo* info, uint64 begSector, int dd); void ntfsx_cluster_release(ntfsx_cluster* clus); @@ -73,7 +73,6 @@ ntfs_attribheader* ntfsx_attribute_header(ntfsx_attribute* attr); void* ntfsx_attribute_getresidentdata(ntfsx_attribute* attr); uint32 ntfsx_attribute_getresidentsize(ntfsx_attribute* attr); ntfsx_datarun* ntfsx_attribute_getdatarun(ntfsx_attribute* attr); -bool ntfsx_attribute_next(ntfsx_attribute* attr, uint32 attrType); @@ -86,12 +85,30 @@ typedef struct _ntfsx_record ntfsx_record; ntfsx_record* ntfsx_record_alloc(partitioninfo* info); +ntfsx_cluster* ntfsx_record_cluster(ntfsx_record* record); void ntfsx_record_free(ntfsx_record* record); bool ntfsx_record_read(ntfsx_record* record, uint64 begSector, int dd); ntfs_recordheader* ntfsx_record_header(ntfsx_record* record); ntfsx_attribute* ntfsx_record_findattribute(ntfsx_record* record, uint32 attrType, int dd); +/* used as a heap based object */ +typedef struct _ntfsx_attrib_enum +{ + ntfs_attribheader* _attrhead; /* The last attribute examined */ + ntfs_attriblistrecord* _listrec; /* The last attr list record examined */ + unsigned char _flags; /* Whether to search through the list first */ + uint32 type; /* The type we're going for */ +} +ntfsx_attrib_enum; + +ntfsx_attrib_enum* ntfsx_attrib_enum_alloc(uint32 type, bool normal); +ntfsx_attribute* ntfsx_attrib_enum_all(ntfsx_attrib_enum* attrenum, ntfsx_record* record); +ntfsx_attribute* ntfsx_attrib_enum_inline(ntfsx_attrib_enum* attrenum, ntfsx_record* record); +ntfsx_attribute* ntfsx_attrib_enum_list(ntfsx_attrib_enum* attrenum, ntfsx_record* record); +void ntfsx_attrib_enum_free(ntfsx_attrib_enum* attrenum); + + /* used as a stack based object */ struct _ntfsx_mftmap_block; diff --git a/src/scrounge.c b/src/scrounge.c index 5627d36..45cd1bf 100644 --- a/src/scrounge.c +++ b/src/scrounge.c @@ -23,6 +23,8 @@ #include "ntfsx.h" #include "locks.h" +#define PROCESS_MFT_FLAG_SUB 1 << 1 + typedef struct _filebasics { fchar_t filename[MAX_PATH + 1]; @@ -38,6 +40,7 @@ void processRecordFileBasics(partitioninfo* pi, ntfsx_record* record, filebasics { /* Data Attribute */ ntfsx_attribute* attr = NULL; + ntfsx_attrib_enum* attrenum = NULL; { byte* resident = NULL; @@ -54,82 +57,84 @@ void processRecordFileBasics(partitioninfo* pi, ntfsx_record* record, filebasics basics->parent = kInvalidSector; /* Now get the name and info... */ - attr = ntfsx_record_findattribute(record, kNTFS_FILENAME, pi->device); - if(!attr) goto cleanup; + attrenum = ntfsx_attrib_enum_alloc(kNTFS_FILENAME, true); nameSpace = kNTFS_NameSpacePOSIX; memset(basics->filename, 0, sizeof(basics->filename)); - do + while((attr = ntfsx_attrib_enum_all(attrenum, record)) != NULL) { /* TODO ASSUMPTION: File name is always resident */ - ASSERT(!ntfsx_attribute_header(attr)->bNonResident); - - - /* Get out all the info we need */ - filename = (ntfs_attribfilename*)ntfsx_attribute_getresidentdata(attr); - ASSERT(filename); + if(!ntfsx_attribute_header(attr)->bNonResident) + { + /* Get out all the info we need */ + filename = (ntfs_attribfilename*)ntfsx_attribute_getresidentdata(attr); + ASSERT(filename); - /* - * There can be multiple filenames with different - * namespaces so choose the best one - */ - if(ntfs_isbetternamespace(nameSpace, filename->nameSpace)) - { - /* Dates */ - basics->created = filename->timeCreated; - basics->modified = filename->timeModified; - basics->accessed = filename->timeRead; - - /* File Name */ - name = (ntfs_char*)(((byte*)filename) + sizeof(ntfs_attribfilename)); - len = filename->cFileName; - if(len > MAX_PATH) - len = MAX_PATH; + /* + * There can be multiple filenames with different + * namespaces so choose the best one + */ + if(ntfs_isbetternamespace(nameSpace, filename->nameSpace)) + { + /* Dates */ + basics->created = filename->timeCreated; + basics->modified = filename->timeModified; + basics->accessed = filename->timeRead; + + /* File Name */ + name = (ntfs_char*)(((byte*)filename) + sizeof(ntfs_attribfilename)); + len = filename->cFileName; + if(len > MAX_PATH) + len = MAX_PATH; #ifdef FC_WIDE - wcsncpy(basics->filename, name, len); + wcsncpy(basics->filename, name, len); #else - temp = unicode_transcode16to8(name, len); - if(!temp) - errx(1, "out of memory"); + temp = unicode_transcode16to8(name, len); - len = strlen(temp); - if(len > MAX_PATH) - len = MAX_PATH; + len = strlen(temp); + if(len > MAX_PATH) + len = MAX_PATH; - strncpy(basics->filename, temp, len); + strncpy(basics->filename, temp, len); #endif - basics->filename[len] = 0; + basics->filename[len] = 0; + + /* Attributes */ + basics->flags = filename->flags; - /* Attributes */ - basics->flags = filename->flags; + /* Parent Directory */ + basics->parent = filename->refParent & kNTFS_RefMask; - /* Parent Directory */ - basics->parent = filename->refParent & kNTFS_RefMask; + /* Namespace */ + nameSpace = filename->nameSpace; + } + } - /* Namespace */ - nameSpace = filename->nameSpace; - } + ntfsx_attribute_free(attr); + attr = NULL; } - while(ntfsx_attribute_next(attr, kNTFS_FILENAME)); } -cleanup: if(attr) ntfsx_attribute_free(attr); + + if(attrenum) + ntfsx_attrib_enum_free(attrenum); } -void processMFTRecord(partitioninfo* pi, uint64 sector, int level) +void processMFTRecord(partitioninfo* pi, uint64 sector, uint32 flags) { ntfsx_record* record = NULL; ntfsx_attribute* attribdata = NULL; + ntfsx_attrib_enum* attrenum = NULL; ntfsx_datarun* datarun = NULL; - int outfile = -1; + int ofile = -1; ntfsx_cluster cluster; memset(&cluster, 0, sizeof(cluster)); @@ -142,6 +147,7 @@ void processMFTRecord(partitioninfo* pi, uint64 sector, int level) uint16 rename = 0; uint64 fileSize; uint32 i; + bool haddata = false; uint32 num; fchar_t filename2[MAX_PATH + 1]; ntfs_attribheader* attrhead; @@ -150,8 +156,6 @@ void processMFTRecord(partitioninfo* pi, uint64 sector, int level) ASSERT(sector != kInvalidSector); record = ntfsx_record_alloc(pi); - if(!record) - errx(1, "out of memory"); /* Read the MFT record */ if(!ntfsx_record_read(record, sector, pi->device)) @@ -166,15 +170,20 @@ void processMFTRecord(partitioninfo* pi, uint64 sector, int level) /* Try and get a file name out of the header */ processRecordFileBasics(pi, record, &basics); + /* Without files we skip */ if(basics.filename[0] == 0) - { - RETWARNX("invalid mft record. in use, but no filename"); - } + RETURN; /* If it's the root folder then return */ if(!fcscmp(basics.filename, FC_DOT)) RETURN; + /* System, Hidden files that begin with $ are skipped */ + if(basics.flags & kNTFS_FileSystem && + basics.flags & kNTFS_FileHidden && + basics.filename[0] == kNTFS_SysPrefix) + RETURN; + /* Process parent folders if available */ if(basics.parent != kInvalidSector) { @@ -186,11 +195,12 @@ void processMFTRecord(partitioninfo* pi, uint64 sector, int level) if(parentSector == kInvalidSector) warnx("invalid parent directory for file: " FC_PRINTF, basics.filename); else - processMFTRecord(pi, parentSector, level + 1); + processMFTRecord(pi, parentSector, flags | PROCESS_MFT_FLAG_SUB); } } - printf(level == 0 ? "\\" FC_PRINTF "\n" : "\\" FC_PRINTF, basics.filename); + printf(flags & PROCESS_MFT_FLAG_SUB ? + "\\" FC_PRINTF : "\\" FC_PRINTF "\n", basics.filename); /* Directory handling: */ if(header->flags & kNTFS_RecFlagDir) @@ -198,154 +208,241 @@ void processMFTRecord(partitioninfo* pi, uint64 sector, int level) /* Try to change to the directory */ if(fc_chdir(basics.filename) == -1) { - if(fc_mkdir(basics.filename) == -1) - { - warnx("couldn't create directory '" FC_PRINTF "' putting files in parent directory", basics.filename); - } - else +#ifdef _DEBUG + if(!g_verifyMode) +#endif { - setFileAttributes(basics.filename, basics.flags); - fc_chdir(basics.filename); + if(fc_mkdir(basics.filename) == -1) + { + warn("couldn't create directory '" FC_PRINTF "' putting files in parent directory", basics.filename); + } + else + { + setFileAttributes(basics.filename, basics.flags); + fc_chdir(basics.filename); + } } } RETURN; } - - /* Normal file handling: */ - outfile = fc_open(basics.filename, O_BINARY | O_CREAT | O_EXCL | O_WRONLY); - - fcsncpy(filename2, basics.filename, MAX_PATH); - filename2[MAX_PATH] = 0; - - while(outfile == -1 && errno == EEXIST && rename < 0x1000) +#ifdef _DEBUG + /* If in verify mode */ + if(g_verifyMode) { - if(fcslen(basics.filename) + 7 >= MAX_PATH) + ofile = fc_open(basics.filename, O_BINARY | O_RDONLY); + + if(ofile == -1) { - warnx("file name too long on duplicate file: " FC_PRINTF, basics.filename); + warn("couldn't open verify file: " FC_PRINTF, basics.filename); goto cleanup; } - - fcscpy(basics.filename, filename2); - fcscat(basics.filename, FC_DOT); - - itofc(rename, basics.filename + fcslen(basics.filename), 10); - rename++; - - outfile = fc_open(basics.filename, O_BINARY | O_CREAT | O_EXCL | O_WRONLY); } - if(outfile == -1) + /* Normal file handling: */ + else +#endif { - warnx("couldn't open output file: " FC_PRINTF, basics.filename); - goto cleanup; - } + ofile = fc_open(basics.filename, O_BINARY | O_CREAT | O_EXCL | O_WRONLY); + + fcsncpy(filename2, basics.filename, MAX_PATH); + filename2[MAX_PATH] = 0; + while(ofile == -1 && errno == EEXIST && rename < 0x1000) + { + if(fcslen(basics.filename) + 7 >= MAX_PATH) + { + warnx("file name too long on duplicate file: " FC_PRINTF, basics.filename); + goto cleanup; + } - attribdata = ntfsx_record_findattribute(record, kNTFS_DATA, pi->device); - if(!attribdata) - RETWARNX("invalid mft record. no data attribute found"); - - attrhead = ntfsx_attribute_header(attribdata); + fcscpy(basics.filename, filename2); + fcscat(basics.filename, FC_DOT); - /* For resident data just write it out */ - if(!attrhead->bNonResident) - { - uint32 length = ntfsx_attribute_getresidentsize(attribdata); - byte* data = ntfsx_attribute_getresidentdata(attribdata); + itofc(rename, basics.filename + fcslen(basics.filename), 10); + rename++; - if(!data) - RETWARNX("invalid mft record. resident data screwed up"); + ofile = fc_open(basics.filename, O_BINARY | O_CREAT | O_EXCL | O_WRONLY); + } - if(write(outfile, data, length) != (int32)length) - RETWARN("couldn't write data to output file"); + if(ofile == -1) + { + warn("couldn't open output file: " FC_PRINTF, basics.filename); + goto cleanup; + } } - /* For non resident data it's a bit more involved */ - else + attrenum = ntfsx_attrib_enum_alloc(kNTFS_DATA, true); + + while((attribdata = ntfsx_attrib_enum_all(attrenum, record)) != NULL) { - datarun = ntfsx_attribute_getdatarun(attribdata); - if(!datarun) - errx(1, "out of memory"); + attrhead = ntfsx_attribute_header(attribdata); - nonres = (ntfs_attribnonresident*)attrhead; - fileSize = nonres->cbAttribData; + /* + * We don't do compressed/encrypted files. Eventually + * we may be able to write in some support :) + */ + if(attrhead->flags & kNTFS_AttrCompressed) + RETWARNX("compressed file. skipping."); - /* Allocate a cluster for reading and writing */ - if(!ntfsx_cluster_reserve(&cluster, pi)) - errx(1, "out of memory"); + if(attrhead->flags & kNTFS_AttrEncrypted) + RETWARNX("encrypted file. skipping."); - if(ntfsx_datarun_first(datarun)) + /* On the first round figure out the file size */ + if(!haddata) { - do + if(attrhead->bNonResident) { - /* Check to see if we have a bogus data run */ - if(fileSize == 0 && datarun->length) - { - warnx("invalid mft record. file length invalid or extra data in file"); - break; - } + nonres = (ntfs_attribnonresident*)attrhead; + fileSize = nonres->cbAttribData; + } + else + { + fileSize = ntfsx_attribute_getresidentsize(attribdata); + } + } - /* Sparse clusters we just write zeros */ - if(datarun->sparse) - { - memset(cluster.data, 0, cluster.size); + haddata = true; - for(i = 0; i < datarun->length && fileSize; i++) - { - num = cluster.size; + /* For resident data just write it out */ + if(!attrhead->bNonResident) + { + uint32 length = ntfsx_attribute_getresidentsize(attribdata); + byte* data = ntfsx_attribute_getresidentdata(attribdata); + + if(!data) + RETWARNX("invalid mft record. resident data screwed up"); - if(fileSize < 0xFFFFFFFF && num > (uint32)fileSize) - num = (uint32)fileSize; +#ifdef _DEBUG + if(g_verifyMode) + { + if(compareFileData(ofile, data, length) != 0) + RETWARNX("verify failed. read file data wrong."); + } + else +#endif + if(write(ofile, data, length) != (int32)length) + RETWARN("couldn't write data to output file"); - if(write(outfile, cluster.data, num) != (int32)num) - err(1, "couldn't write to output file: " FC_PRINTF, basics.filename); + fileSize -= length; + } - fileSize -= num; - } - } + /* For non resident data it's a bit more involved */ + else + { + datarun = ntfsx_attribute_getdatarun(attribdata); + nonres = (ntfs_attribnonresident*)attrhead; - /* Handle not sparse clusters */ - else + /* Allocate a cluster for reading and writing */ + ntfsx_cluster_reserve(&cluster, pi); + + if(ntfsx_datarun_first(datarun)) + { + do { - if(pi->locks) + /* + * In some cases NTFS sloppily leaves many extra + * data runs mapped for a file, so just cut out + * when that's the case + */ + if(fileSize == 0) + break; + + /* Sparse clusters we just write zeros */ + if(datarun->sparse) { - /* Add a location lock so any raw scrounging won't do - this cluster later */ - addLocationLock(pi->locks, CLUSTER_TO_SECTOR(*pi, datarun->cluster), - CLUSTER_TO_SECTOR(*pi, datarun->cluster + datarun->length)); + memset(cluster.data, 0, cluster.size); + + for(i = 0; i < datarun->length && fileSize; i++) + { + num = cluster.size; + + if(fileSize < 0xFFFFFFFF && num > (uint32)fileSize) + num = (uint32)fileSize; + +#ifdef _DEBUG + if(g_verifyMode) + { + if(compareFileData(ofile, cluster.data, num) != 0) + RETWARNX("verify failed. read file data wrong."); + } + else +#endif + if(write(ofile, cluster.data, num) != (int32)num) + err(1, "couldn't write to output file: " FC_PRINTF, basics.filename); + + fileSize -= num; + } } - for(i = 0; i < datarun->length && fileSize; i++) + /* Handle not sparse clusters */ + else { - num = min(cluster.size, (uint32)fileSize); - dataSector = CLUSTER_TO_SECTOR(*pi, (datarun->cluster + i)); - - if(!ntfsx_cluster_read(&cluster, pi, dataSector, pi->device)) - err(1, "couldn't read sector from disk"); - - if(write(outfile, cluster.data, num) != (int32)num) - err(1, "couldn't write to output file: " FC_PRINTF, basics.filename); + if(pi->locks) + { + /* Add a location lock so any raw scrounging won't do + this cluster later */ + addLocationLock(pi->locks, CLUSTER_TO_SECTOR(*pi, datarun->cluster), + CLUSTER_TO_SECTOR(*pi, datarun->cluster + datarun->length)); + } + + for(i = 0; i < datarun->length && fileSize; i++) + { + num = min(cluster.size, (uint32)fileSize); + dataSector = CLUSTER_TO_SECTOR(*pi, (datarun->cluster + i)); + + if(!ntfsx_cluster_read(&cluster, pi, dataSector, pi->device)) + err(1, "couldn't read sector from disk"); + +#ifdef _DEBUG + if(g_verifyMode) + { + if(compareFileData(ofile, cluster.data, num) != 0) + RETWARNX("verify failed. read file data wrong."); + } + else +#endif + if(write(ofile, cluster.data, num) != (int32)num) + err(1, "couldn't write to output file: " FC_PRINTF, basics.filename); - fileSize -= num; + fileSize -= num; + } } } + while(ntfsx_datarun_next(datarun)); } - while(ntfsx_datarun_next(datarun)); + + ntfsx_datarun_free(datarun); + datarun = NULL; } + + ntfsx_attribute_free(attribdata); + attribdata = NULL; + + /* Cut out when there's extra clusters allocated */ + if(fileSize == 0) + break; } + if(!haddata) + RETWARNX("invalid mft record. no data attribute found"); + if(fileSize != 0) warnx("invalid mft record. couldn't find all data for file"); - close(outfile); - outfile = -1; + close(ofile); + ofile = -1; - setFileTime(basics.filename, &(basics.created), - &(basics.accessed), &(basics.modified)); +#ifdef _DEBUG + if(!g_verifyMode) +#endif + { + setFileTime(basics.filename, &(basics.created), + &(basics.accessed), &(basics.modified)); - setFileAttributes(basics.filename, basics.flags); + setFileAttributes(basics.filename, basics.flags); + } } cleanup: @@ -360,8 +457,11 @@ cleanup: if(datarun) ntfsx_datarun_free(datarun); - if(outfile != -1) - close(outfile); + if(attrenum) + ntfsx_attrib_enum_free(attrenum); + + if(ofile != -1) + close(ofile); } @@ -379,8 +479,6 @@ void scroungeMFT(partitioninfo* pi, ntfsx_mftmap* map) errx(2, "invalid mft. past end of partition"); record = ntfsx_record_alloc(pi); - if(!record) - errx(1, "out of memory"); /* Read the MFT record */ if(!ntfsx_record_read(record, sector, pi->device)) @@ -392,6 +490,11 @@ void scroungeMFT(partitioninfo* pi, ntfsx_mftmap* map) if(!(header->flags & kNTFS_RecFlagUse)) errx(2, "invalid mft. marked as not in use"); + /* Load the MFT data runs */ + + if(!ntfsx_mftmap_load(map, record, pi->device)) + err(1, "error reading in mft"); + /* Try and get a file name out of the header */ processRecordFileBasics(pi, record, &basics); @@ -401,11 +504,6 @@ void scroungeMFT(partitioninfo* pi, ntfsx_mftmap* map) fprintf(stderr, "[Processing MFT...]\n"); - /* Load the MFT data runs */ - - if(!ntfsx_mftmap_load(map, record, pi->device)) - err(1, "error reading in mft"); - if(ntfsx_mftmap_length(map) == 0) errx(1, "invalid mft. no records in mft"); diff --git a/src/scrounge.h b/src/scrounge.h index 6ebed43..9e9f37c 100644 --- a/src/scrounge.h +++ b/src/scrounge.h @@ -34,4 +34,10 @@ void scroungeUsingRaw(partitioninfo* pi); void setFileAttributes(fchar_t* filename, uint32 flags); void setFileTime(fchar_t* filename, uint64* created, uint64* accessed, uint64* modified); +int compareFileData(int f, void* data, size_t length); + +#ifdef _DEBUG + extern bool g_verifyMode; +#endif + #endif /* __SCROUNGE_H__ */ diff --git a/src/unicode.c b/src/unicode.c index b715a47..a2dcacb 100644 --- a/src/unicode.c +++ b/src/unicode.c @@ -19,8 +19,7 @@ char* unicode_transcode16to8(const ntfs_char* src, size_t len) /* Allocate 1.25 times the length initially */ alloc = len + (len / 4) + 1; - ret = (char*)malloc(alloc * sizeof(char)); - if(!ret) return NULL; + ret = (char*)mallocf(alloc * sizeof(char)); c = src; e = c + len; @@ -31,8 +30,7 @@ char* unicode_transcode16to8(const ntfs_char* src, size_t len) if(pos + 4 >= alloc) { alloc += (len / 2) + 1; - if(!(ret = (char*)reallocf(ret, alloc * sizeof(char)))) - return NULL; + ret = (char*)reallocf(ret, alloc * sizeof(char)); } /* Encode as one character */ -- cgit v1.2.3