mirror of
https://git.code.sf.net/p/ntfs-3g/ntfs-3g.git
synced 2024-11-30 21:44:21 +08:00
69306cc493
(Logical change 1.36)
450 lines
13 KiB
Plaintext
450 lines
13 KiB
Plaintext
/* Reverse engineered functions in more or less modified form. find_attr()
|
|
* is quite heavily modified but should be functionally equivalent to original.
|
|
* lookup and lookup_external are less modified. Both should be functionally
|
|
* equivalent to originals. */
|
|
|
|
/*
|
|
* attr_search_context - used in attribute search functions
|
|
* @mrec: buffer containing mft record to search
|
|
* @attr: attribute record in @mrec where to begin/continue search
|
|
* @alist_mrec: mft record containing attribute list (i.e. base mft record)
|
|
* @alist_attr: attribute list attribute record
|
|
* @alist_val: attribute list value (if alist is resident in @alist_mrec)
|
|
* @alist_val_end: end of attribute list value + 1
|
|
* @alist_val_len: length of attribute list in bytes
|
|
* @is_first: if true lookup_attr() begins search with @attr, else after @attr
|
|
*
|
|
* Structure must be initialized to zero before the first call to one of the
|
|
* attribute search functions. If the mft record in which to search has already
|
|
* been loaded into memory, then initialize @base and @mrec to point to it,
|
|
* @attr to point to the first attribute within @mrec, and set @is_first to
|
|
* TRUE.
|
|
*
|
|
* @is_first is only honoured in lookup_attr() and only when called with @mrec
|
|
* not NULL. Then, if @is_first is TRUE, lookup_attr() begins the search with
|
|
* @attr. If @is_first is FALSE, lookup_attr() begins the search after @attr.
|
|
* This is so that, after the first call to lookup_attr(), we can call
|
|
* lookup_attr() again, without any modification of the search context, to
|
|
* automagically get the next matching attribute.
|
|
*
|
|
* In contrast, find_attr() ignores @is_first and always begins the search with
|
|
* @attr. find_attr() shouldn't really be called directly; it is just for
|
|
* internal use. FIXME: Might want to change this behaviour later, but not
|
|
* before I am finished with lookup_external_attr(). (AIA)
|
|
*/
|
|
typedef struct {
|
|
u8 *base;
|
|
MFT_RECORD *mrec;
|
|
ATTR_RECORD *attr;
|
|
|
|
u8 *alist_val_base;
|
|
MFT_RECORD *alist_mrec;
|
|
ATTR_RECORD *alist_attr;
|
|
ATTR_LIST_ENTRY *alist_val;
|
|
ATTR_LIST_ENTRY *alist_val_end;
|
|
u32 alist_val_len;
|
|
IS_FIRST_BOOL is_first;
|
|
u8 *alist_old_base;
|
|
} attr_search_context;
|
|
|
|
BOOL attr_find(const ntfs_volume *vol, const ATTR_TYPES type,
|
|
const wchar_t *name, const u32 name_len,
|
|
const IGNORE_CASE_BOOL ic, const u8 *val, const u32 val_len,
|
|
ntfs_attr_search_ctx *ctx)
|
|
{
|
|
ATTR_RECORD *a;
|
|
|
|
#ifdef DEBUG
|
|
if (!vol || !ctx || !ctx->mrec || !ctx->attr) {
|
|
printf(stderr, "attr_find() received NULL pointer!\n");
|
|
return FALSE;
|
|
}
|
|
#endif
|
|
a = ctx->attr;
|
|
/*
|
|
* Iterate over attributes in mft record starting at @ctx->attr.
|
|
* Note: Not using while/do/for loops so the comparison code
|
|
* does not get indented out of the 80 characters wide screen... (AIA)
|
|
*/
|
|
goto search_loop;
|
|
do_next:
|
|
a = (ATTR_RECORD*)((char*)a + le32_to_cpu(a->length));
|
|
if (a < ctx->mrec || a > (char*)ctx->mrec + vol->mft_record_size)
|
|
goto file_corrupt;
|
|
ctx->attr = a;
|
|
search_loop:
|
|
/* We catch $END with this more general check, too... */
|
|
if (le32_to_cpu(a->type) > le32_to_cpu(type))
|
|
goto not_found;
|
|
if (!a->length)
|
|
goto file_corrupt;
|
|
if (a->type != type)
|
|
goto do_next;
|
|
/* If no @name is specified, check for @val. */
|
|
if (!name) {
|
|
register int rv;
|
|
/* If no @val specified, we are done. */
|
|
if (!val) {
|
|
found_it:
|
|
return TRUE;
|
|
}
|
|
rv = memcmp(val, (char*)a + le16_to_cpu(a->value_offset),
|
|
min(val_len, le32_to_cpu(a->value_length)));
|
|
/* If @val collates after the current attribute's value,
|
|
continue searching as a matching attribute might follow. */
|
|
if (!rv) {
|
|
register u32 avl = le32_to_cpu(a->value_length);
|
|
if (val_len == avl)
|
|
goto found_it;
|
|
if (val_len > avl)
|
|
goto do_next;
|
|
} else if (rv > 0)
|
|
goto do_next;
|
|
goto not_found;
|
|
}
|
|
if (ntfs_names_are_equal(name, name_len,
|
|
(wchar_t*)((char*)a + le16_to_cpu(a->name_offset)),
|
|
a->name_length, ic, vol->upcase, vol->upcase_len))
|
|
goto found_it;
|
|
{ register int rc = ntfs_names_collate(vol->upcase,
|
|
vol->upcase_len, name, name_len,
|
|
(wchar_t*)((char*)a + le16_to_cpu(a->name_offset)),
|
|
a->name_length, IGNORE_CASE, 1);
|
|
/* If case insensitive collation of names collates @name
|
|
before a->name, there is no matching attribute. */
|
|
if (rc == -1)
|
|
goto not_found;
|
|
/* If the strings are not equal, continue search. */
|
|
if (rc)
|
|
goto do_next;
|
|
}
|
|
/* If case sensitive collation of names doesn't collate @name before
|
|
a->name, we continue the search. Otherwise we haven't found it. */
|
|
if (ntfs_names_collate(vol->upcase, vol->upcase_len, name, name_len,
|
|
(wchar_t*)((char*)a + le16_to_cpu(a->name_offset)),
|
|
a->name_length, CASE_SENSITIVE, 1) != -1)
|
|
goto do_next;
|
|
not_found:
|
|
return FALSE;
|
|
file_corrupt:
|
|
#ifdef DEBUG
|
|
printf(stderr, "find_attr(): File is corrupt. Run chkdsk.\n");
|
|
#endif
|
|
goto not_found;
|
|
}
|
|
|
|
BOOL external_attr_lookup(const ntfs_volume *vol, const MFT_REFERENCE mref,
|
|
const ATTR_TYPES type, const wchar_t *name,
|
|
const u32 name_len, const IGNORE_CASE_BOOL ic,
|
|
const s64 lowest_vcn, const u8 *val,
|
|
const u32 val_len, ntfs_attr_search_ctx *ctx)
|
|
{
|
|
ATTR_LIST_ENTRY *al_pos, **al_val, *al_val_start, *al_next_pos;
|
|
ATTR_RECORD *attr_pos;
|
|
MFT_RECORD *mrec, *m;
|
|
u8 var1 = 0;
|
|
u8 var2 = 0;
|
|
u8 var3;
|
|
int rc;
|
|
wchar_t *al_name;
|
|
u32 al_name_len;
|
|
|
|
al_val = &ctx->alist_val;
|
|
if (ctx->alist_val_end <= *al_val && !ctx->is_first)
|
|
goto file_corrupt;
|
|
al_val_start = 0;
|
|
if (ctx->base) {
|
|
if (ctx->is_first)
|
|
goto already_have_the_base_and_is_first;
|
|
al_val_start = *al_val;
|
|
al_pos = (char*)*al_val + le16_to_cpu((*al_val)->length);
|
|
} else
|
|
al_pos = *al_val;
|
|
do_next:
|
|
if (al_pos < ctx->alist_val_end)
|
|
goto al_pos_below_alist_val_end;
|
|
var1 = var2 = 1;
|
|
al_pos = *al_val;
|
|
do_next_2:
|
|
*al_val = al_pos;
|
|
if (!type || var1 || type == al_pos->type)
|
|
goto compare_names;
|
|
if (le32_to_cpu(al_pos->type) > le32_to_cpu(type))
|
|
goto gone_too_far;
|
|
al_pos = al_next_pos;
|
|
goto do_next;
|
|
already_have_the_base_and_is_first:
|
|
ctx->is_first = FALSE;
|
|
if (*al_val < ctx->alist_val_end)
|
|
goto do_next;
|
|
if (ctx->base) {
|
|
// FIXME: CcUnpinData(ctx->base);
|
|
ctx->base = NULL;
|
|
}
|
|
if (!type)
|
|
return FALSE;
|
|
if (ntfs_file_record_read(vol, mref, &ctx->mrec, &ctx->attr) < 0)
|
|
return FALSE;
|
|
ctx->base = ctx->mrec;
|
|
attr_find(vol, type, name, name_len, ic, val, val_len, ctx);
|
|
return FALSE;
|
|
al_pos_below_alist_val_end:
|
|
if (al_pos < ctx->alist_val)
|
|
goto file_corrupt;
|
|
if (al_pos >= ctx->alist_val_end)
|
|
goto file_corrupt;
|
|
if (!al_pos->length)
|
|
goto file_corrupt;
|
|
al_next_pos = (ATTR_LIST_ENTRY*)((char*)al_pos +
|
|
le16_to_cpu(al_pos->length));
|
|
goto do_next_2;
|
|
gone_too_far:
|
|
var1 = 1;
|
|
compare_names:
|
|
al_name_len = al_pos->name_length;
|
|
al_name = (wchar_t*)((char*)al_pos + al_pos->name_offset);
|
|
if (!name || var1)
|
|
goto compare_lowest_vcn;
|
|
if (ic == CASE_SENSITIVE) {
|
|
if (name_len == al_name_len &&
|
|
!memcmp(al_name, name, al_name_len << 1))
|
|
rc = TRUE;
|
|
else
|
|
rc = FALSE;
|
|
} else /* IGNORE_CASE */
|
|
rc = ntfs_names_are_equal(al_name, al_name_len, name, name_len,
|
|
ic, vol->upcase, vol->upcase_len);
|
|
if (rc)
|
|
goto compare_lowest_vcn;
|
|
rc = ntfs_names_collate(vol->upcase, vol->upcase_len, name, name_len,
|
|
al_name, al_name_len, IGNORE_CASE, 1);
|
|
if (rc == -1)
|
|
goto name_collates_before_al_name;
|
|
if (!rc && ntfs_names_collate(vol->upcase, vol->upcase_len, name,
|
|
name_len, al_name, al_name_len,
|
|
IGNORE_CASE, 0) == -1)
|
|
goto name_collates_before_al_name;
|
|
al_pos = al_next_pos;
|
|
goto do_next;
|
|
name_collates_before_al_name:
|
|
var1 = 1;
|
|
compare_lowest_vcn:
|
|
if (lowest_vcn && !var1 && al_next_pos < ctx->alist_val_end &&
|
|
sle64_to_cpu(al_next_pos->lowest_vcn) <= sle64_to_cpu(lowest_vcn) &&
|
|
al_next_pos->type == al_pos->type &&
|
|
al_next_pos->name_length == al_name_len &&
|
|
!memcmp((char*)al_next_pos + al_next_pos->name_offset, al_name,
|
|
al_name_len << 1)) {
|
|
al_pos = al_next_pos;
|
|
goto do_next;
|
|
}
|
|
/* Don't mask the sequence number. If it isn't equal, the ref is stale.
|
|
*/
|
|
if (al_val_start &&
|
|
al_pos->mft_reference == al_val_start->mft_reference) {
|
|
mrec = ctx->mrec;
|
|
attr_pos = (ATTR_RECORD*)((char*)mrec +
|
|
le16_to_cpu(mrec->attrs_offset));
|
|
} else {
|
|
if (ctx->base) {
|
|
// FIXME: CcUnpinData(ctx->base);
|
|
ctx->base = 0;
|
|
}
|
|
if (ntfs_file_record_read(vol,
|
|
le64_to_cpu(al_pos->mft_reference),
|
|
&m, &attr_pos) < 0)
|
|
return FALSE;
|
|
mrec = ctx->mrec;
|
|
ctx->base = ctx->mrec = m;
|
|
}
|
|
var3 = 0;
|
|
do_next_attr_loop_start:
|
|
if (attr_pos < mrec || attr_pos > (char*)mrec + vol->mft_record_size)
|
|
goto file_corrupt;
|
|
if (attr_pos->type == AT_END)
|
|
goto do_next_al_entry;
|
|
if (!attr_pos->length)
|
|
goto file_corrupt;
|
|
if (al_pos->instance != attr_pos->instance)
|
|
goto do_next_attr;
|
|
if (al_pos->type != attr_pos->type)
|
|
goto do_next_al_entry;
|
|
if (!name)
|
|
goto skip_name_comparison;
|
|
if (attr_pos->name_length != al_name_len)
|
|
goto do_next_al_entry;
|
|
if (memcmp((wchar_t*)((char*)attr_pos +
|
|
le16_to_cpu(attr_pos->name_offset)), al_name,
|
|
attr_pos->name_length << 1))
|
|
goto do_next_al_entry;
|
|
skip_name_comparison:
|
|
var3 = 1;
|
|
ctx->attr = attr_pos;
|
|
if (var1)
|
|
goto loc_5217c;
|
|
if (!val)
|
|
return TRUE;
|
|
if (attr_pos->non_resident)
|
|
goto do_next_attr;
|
|
if (le32_to_cpu(attr_pos->value_length) != val_len)
|
|
goto do_next_attr;
|
|
if (!memcmp((char*)attr_pos + le16_to_cpu(attr_pos->value_offset),
|
|
val, val_len))
|
|
return TRUE;
|
|
do_next_attr:
|
|
attr_pos = (ATTR_RECORD*)((char*)attr_pos +
|
|
le32_to_cpu(attr_pos->length));
|
|
goto do_next_attr_loop_start;
|
|
do_next_al_entry:
|
|
if (!var3)
|
|
goto file_corrupt;
|
|
al_pos = (ATTR_RECORD*)((char*)al_pos + le16_to_cpu(al_pos->length));
|
|
goto do_next;
|
|
loc_5217c:
|
|
if (var2)
|
|
*al_val = (ATTR_RECORD*)((char*)al_pos +
|
|
le16_to_cpu(al_pos->length));
|
|
if (ctx->base) {
|
|
// FIXME: CcUnpinData(ctx->base);
|
|
ctx->base = 0;
|
|
}
|
|
if (!type)
|
|
return FALSE;
|
|
if (ntfs_file_record_read(vol, mref, &mrec, &ctx->attr) < 0)
|
|
return FALSE;
|
|
ctx->base = mrec;
|
|
attr_find(vol, type, name, name_len, ic, val, val_len, ctx);
|
|
return FALSE;
|
|
file_corrupt:
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "lookup_attr() encountered corrupt file record.\n");
|
|
#endif
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL attr_lookup(const ntfs_volume *vol, const MFT_REFERENCE *mref,
|
|
const ATTR_TYPES type, const wchar_t *name,
|
|
const u32 name_len, const IGNORE_CASE_BOOL ic,
|
|
const s64 lowest_vcn, const u8 *val, const u32 val_len,
|
|
ntfs_attr_search_ctx *ctx)
|
|
{
|
|
MFT_RECORD *m;
|
|
ATTR_RECORD *a;
|
|
s64 len;
|
|
|
|
if (!vol || !ctx) {
|
|
#ifdef DEBUG
|
|
printf(stderr, "lookup_attr() received NULL pointer!\n");
|
|
#endif
|
|
return FALSE;
|
|
}
|
|
if (ctx->base)
|
|
goto already_have_the_base;
|
|
if (ntfs_file_record_read(vol, mref, &m, &a) < 0)
|
|
return FALSE;
|
|
ctx->base = ctx->mrec = m;
|
|
ctx->attr = a;
|
|
ctx->alist_mrec = ctx->alist_attr = ctx->alist_val = NULL;
|
|
/*
|
|
* Look for an attribute list and at the same time check for attributes
|
|
* which collate before the attribute list (i.e. $STANDARD_INFORMATION).
|
|
*/
|
|
if (le32_to_cpu(a->type) > le32_to_cpu(AT_ATTRIBUTE_LIST))
|
|
goto no_attr_list;
|
|
do_next:
|
|
if (!a->length)
|
|
goto file_corrupt;
|
|
if (a->type == AT_ATTRIBUTE_LIST)
|
|
goto attr_list_present;
|
|
a = (ATTR_RECORD*)((char*)a + le32_to_cpu(a->length));
|
|
if (a < m || a > (char*)m + vol->mft_record_size)
|
|
goto file_corrupt;
|
|
if (le32_to_cpu(a->type) <= le32_to_cpu(AT_ATTRIBUTE_LIST))
|
|
goto do_next;
|
|
no_attr_list:
|
|
if (!type || type == AT_STANDARD_INFORMATION &&
|
|
a->type == AT_STANDARD_INFORMATION)
|
|
goto found_it;
|
|
call_find_attr:
|
|
return attr_find(vol, type, name, name_len, ic, val, val_len, ctx);
|
|
found_it:
|
|
ctx->attr = a;
|
|
return TRUE;
|
|
already_have_the_base:
|
|
/*
|
|
* If ctx->is_first, search starting with ctx->attr. Otherwise
|
|
* continue search after ctx->attr.
|
|
*/
|
|
if (ctx->is_first) {
|
|
a = ctx->attr;
|
|
ctx->is_first = 0;
|
|
} else
|
|
a = (ATTR_RECORD*)((char*)ctx->attr +
|
|
le32_to_cpu(ctx->attr->length));
|
|
if (a < m || a > (char*)m + vol->mft_record_size)
|
|
goto file_corrupt;
|
|
if (a->type == AT_END)
|
|
return FALSE;
|
|
if (!a->length)
|
|
goto file_corrupt;
|
|
if (type)
|
|
goto call_find_attr;
|
|
goto found_it;
|
|
attr_list_present:
|
|
/*
|
|
* Looking for zero means we return the first attribute, which will
|
|
* be the first one listed in the attribute list.
|
|
*/
|
|
ctx->attr = a;
|
|
if (!type)
|
|
goto search_attr_list;
|
|
if (type == AT_ATTRIBUTE_LIST)
|
|
return TRUE;
|
|
search_attr_list:
|
|
/*
|
|
* "a" contains the attribute list attribute at this stage.
|
|
*/
|
|
ctx->alist_attr = a;
|
|
len = ntfs_get_attribute_value_length(a);
|
|
#ifdef DEBUG
|
|
if (len > 0x40000LL) {
|
|
printf(stderr, "lookup_attr() found corrupt attribute list.\n");
|
|
return FALSE;
|
|
}
|
|
#endif
|
|
ctx->alist_val_len = len;
|
|
if (!(ctx->alist_val = malloc(ctx->alist_val_len))) {
|
|
#ifdef DEBUG
|
|
printf(stderr, "lookup_attr() failed to allocate memory for "
|
|
"attribute list value.\n");
|
|
#endif
|
|
return FALSE;
|
|
}
|
|
if (ntfs_get_attribute_value(vol, ctx->mrec, a, ctx->alist_val) !=
|
|
ctx->alist_val_len) {
|
|
#ifdef DEBUG
|
|
printf(stderr, "lookup_attr() failed to read attribute list "
|
|
"value.\n");
|
|
#endif
|
|
return FALSE;
|
|
}
|
|
ctx->alist_val_end = (char*)ctx->alist_val + ctx->alist_val_len;
|
|
if (a->non_resident) {
|
|
ctx->alist_old_base = ctx->alist_val_base;
|
|
ctx->alist_val_base = ctx->base;
|
|
ctx->base = NULL;
|
|
} else if (ctx->base) {
|
|
// FIXME: CcUnpinData(ctx->base);
|
|
ctx->base = NULL;
|
|
}
|
|
lookup_external:
|
|
return external_attr_lookup(vol, mref, type, name, name_len, ic,
|
|
lowest_vcn, val, val_len, ctx);
|
|
file_corrupt:
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "attr_lookup() encountered corrupt file record.\n");
|
|
#endif
|
|
return FALSE;
|
|
}
|
|
|