mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
602 lines
12 KiB
602 lines
12 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* |
|
* Copyright (C) 2019-2021 Paragon Software GmbH, All rights reserved. |
|
* |
|
*/ |
|
|
|
#include <linux/fs.h> |
|
|
|
#include "debug.h" |
|
#include "ntfs.h" |
|
#include "ntfs_fs.h" |
|
|
|
static inline int compare_attr(const struct ATTRIB *left, enum ATTR_TYPE type, |
|
const __le16 *name, u8 name_len, |
|
const u16 *upcase) |
|
{ |
|
/* First, compare the type codes. */ |
|
int diff = le32_to_cpu(left->type) - le32_to_cpu(type); |
|
|
|
if (diff) |
|
return diff; |
|
|
|
/* They have the same type code, so we have to compare the names. */ |
|
return ntfs_cmp_names(attr_name(left), left->name_len, name, name_len, |
|
upcase, true); |
|
} |
|
|
|
/* |
|
* mi_new_attt_id |
|
* |
|
* Return: Unused attribute id that is less than mrec->next_attr_id. |
|
*/ |
|
static __le16 mi_new_attt_id(struct mft_inode *mi) |
|
{ |
|
u16 free_id, max_id, t16; |
|
struct MFT_REC *rec = mi->mrec; |
|
struct ATTRIB *attr; |
|
__le16 id; |
|
|
|
id = rec->next_attr_id; |
|
free_id = le16_to_cpu(id); |
|
if (free_id < 0x7FFF) { |
|
rec->next_attr_id = cpu_to_le16(free_id + 1); |
|
return id; |
|
} |
|
|
|
/* One record can store up to 1024/24 ~= 42 attributes. */ |
|
free_id = 0; |
|
max_id = 0; |
|
|
|
attr = NULL; |
|
|
|
for (;;) { |
|
attr = mi_enum_attr(mi, attr); |
|
if (!attr) { |
|
rec->next_attr_id = cpu_to_le16(max_id + 1); |
|
mi->dirty = true; |
|
return cpu_to_le16(free_id); |
|
} |
|
|
|
t16 = le16_to_cpu(attr->id); |
|
if (t16 == free_id) { |
|
free_id += 1; |
|
attr = NULL; |
|
} else if (max_id < t16) |
|
max_id = t16; |
|
} |
|
} |
|
|
|
int mi_get(struct ntfs_sb_info *sbi, CLST rno, struct mft_inode **mi) |
|
{ |
|
int err; |
|
struct mft_inode *m = kzalloc(sizeof(struct mft_inode), GFP_NOFS); |
|
|
|
if (!m) |
|
return -ENOMEM; |
|
|
|
err = mi_init(m, sbi, rno); |
|
if (err) { |
|
kfree(m); |
|
return err; |
|
} |
|
|
|
err = mi_read(m, false); |
|
if (err) { |
|
mi_put(m); |
|
return err; |
|
} |
|
|
|
*mi = m; |
|
return 0; |
|
} |
|
|
|
void mi_put(struct mft_inode *mi) |
|
{ |
|
mi_clear(mi); |
|
kfree(mi); |
|
} |
|
|
|
int mi_init(struct mft_inode *mi, struct ntfs_sb_info *sbi, CLST rno) |
|
{ |
|
mi->sbi = sbi; |
|
mi->rno = rno; |
|
mi->mrec = kmalloc(sbi->record_size, GFP_NOFS); |
|
if (!mi->mrec) |
|
return -ENOMEM; |
|
|
|
return 0; |
|
} |
|
|
|
/* |
|
* mi_read - Read MFT data. |
|
*/ |
|
int mi_read(struct mft_inode *mi, bool is_mft) |
|
{ |
|
int err; |
|
struct MFT_REC *rec = mi->mrec; |
|
struct ntfs_sb_info *sbi = mi->sbi; |
|
u32 bpr = sbi->record_size; |
|
u64 vbo = (u64)mi->rno << sbi->record_bits; |
|
struct ntfs_inode *mft_ni = sbi->mft.ni; |
|
struct runs_tree *run = mft_ni ? &mft_ni->file.run : NULL; |
|
struct rw_semaphore *rw_lock = NULL; |
|
|
|
if (is_mounted(sbi)) { |
|
if (!is_mft) { |
|
rw_lock = &mft_ni->file.run_lock; |
|
down_read(rw_lock); |
|
} |
|
} |
|
|
|
err = ntfs_read_bh(sbi, run, vbo, &rec->rhdr, bpr, &mi->nb); |
|
if (rw_lock) |
|
up_read(rw_lock); |
|
if (!err) |
|
goto ok; |
|
|
|
if (err == -E_NTFS_FIXUP) { |
|
mi->dirty = true; |
|
goto ok; |
|
} |
|
|
|
if (err != -ENOENT) |
|
goto out; |
|
|
|
if (rw_lock) { |
|
ni_lock(mft_ni); |
|
down_write(rw_lock); |
|
} |
|
err = attr_load_runs_vcn(mft_ni, ATTR_DATA, NULL, 0, &mft_ni->file.run, |
|
vbo >> sbi->cluster_bits); |
|
if (rw_lock) { |
|
up_write(rw_lock); |
|
ni_unlock(mft_ni); |
|
} |
|
if (err) |
|
goto out; |
|
|
|
if (rw_lock) |
|
down_read(rw_lock); |
|
err = ntfs_read_bh(sbi, run, vbo, &rec->rhdr, bpr, &mi->nb); |
|
if (rw_lock) |
|
up_read(rw_lock); |
|
|
|
if (err == -E_NTFS_FIXUP) { |
|
mi->dirty = true; |
|
goto ok; |
|
} |
|
if (err) |
|
goto out; |
|
|
|
ok: |
|
/* Check field 'total' only here. */ |
|
if (le32_to_cpu(rec->total) != bpr) { |
|
err = -EINVAL; |
|
goto out; |
|
} |
|
|
|
return 0; |
|
|
|
out: |
|
return err; |
|
} |
|
|
|
struct ATTRIB *mi_enum_attr(struct mft_inode *mi, struct ATTRIB *attr) |
|
{ |
|
const struct MFT_REC *rec = mi->mrec; |
|
u32 used = le32_to_cpu(rec->used); |
|
u32 t32, off, asize; |
|
u16 t16; |
|
|
|
if (!attr) { |
|
u32 total = le32_to_cpu(rec->total); |
|
|
|
off = le16_to_cpu(rec->attr_off); |
|
|
|
if (used > total) |
|
return NULL; |
|
|
|
if (off >= used || off < MFTRECORD_FIXUP_OFFSET_1 || |
|
!IS_ALIGNED(off, 4)) { |
|
return NULL; |
|
} |
|
|
|
/* Skip non-resident records. */ |
|
if (!is_rec_inuse(rec)) |
|
return NULL; |
|
|
|
attr = Add2Ptr(rec, off); |
|
} else { |
|
/* Check if input attr inside record. */ |
|
off = PtrOffset(rec, attr); |
|
if (off >= used) |
|
return NULL; |
|
|
|
asize = le32_to_cpu(attr->size); |
|
if (asize < SIZEOF_RESIDENT) { |
|
/* Impossible 'cause we should not return such attribute. */ |
|
return NULL; |
|
} |
|
|
|
attr = Add2Ptr(attr, asize); |
|
off += asize; |
|
} |
|
|
|
asize = le32_to_cpu(attr->size); |
|
|
|
/* Can we use the first field (attr->type). */ |
|
if (off + 8 > used) { |
|
static_assert(ALIGN(sizeof(enum ATTR_TYPE), 8) == 8); |
|
return NULL; |
|
} |
|
|
|
if (attr->type == ATTR_END) { |
|
/* End of enumeration. */ |
|
return NULL; |
|
} |
|
|
|
/* 0x100 is last known attribute for now. */ |
|
t32 = le32_to_cpu(attr->type); |
|
if ((t32 & 0xf) || (t32 > 0x100)) |
|
return NULL; |
|
|
|
/* Check boundary. */ |
|
if (off + asize > used) |
|
return NULL; |
|
|
|
/* Check size of attribute. */ |
|
if (!attr->non_res) { |
|
if (asize < SIZEOF_RESIDENT) |
|
return NULL; |
|
|
|
t16 = le16_to_cpu(attr->res.data_off); |
|
|
|
if (t16 > asize) |
|
return NULL; |
|
|
|
t32 = le32_to_cpu(attr->res.data_size); |
|
if (t16 + t32 > asize) |
|
return NULL; |
|
|
|
return attr; |
|
} |
|
|
|
/* Check some nonresident fields. */ |
|
if (attr->name_len && |
|
le16_to_cpu(attr->name_off) + sizeof(short) * attr->name_len > |
|
le16_to_cpu(attr->nres.run_off)) { |
|
return NULL; |
|
} |
|
|
|
if (attr->nres.svcn || !is_attr_ext(attr)) { |
|
if (asize + 8 < SIZEOF_NONRESIDENT) |
|
return NULL; |
|
|
|
if (attr->nres.c_unit) |
|
return NULL; |
|
} else if (asize + 8 < SIZEOF_NONRESIDENT_EX) |
|
return NULL; |
|
|
|
return attr; |
|
} |
|
|
|
/* |
|
* mi_find_attr - Find the attribute by type and name and id. |
|
*/ |
|
struct ATTRIB *mi_find_attr(struct mft_inode *mi, struct ATTRIB *attr, |
|
enum ATTR_TYPE type, const __le16 *name, |
|
size_t name_len, const __le16 *id) |
|
{ |
|
u32 type_in = le32_to_cpu(type); |
|
u32 atype; |
|
|
|
next_attr: |
|
attr = mi_enum_attr(mi, attr); |
|
if (!attr) |
|
return NULL; |
|
|
|
atype = le32_to_cpu(attr->type); |
|
if (atype > type_in) |
|
return NULL; |
|
|
|
if (atype < type_in) |
|
goto next_attr; |
|
|
|
if (attr->name_len != name_len) |
|
goto next_attr; |
|
|
|
if (name_len && memcmp(attr_name(attr), name, name_len * sizeof(short))) |
|
goto next_attr; |
|
|
|
if (id && *id != attr->id) |
|
goto next_attr; |
|
|
|
return attr; |
|
} |
|
|
|
int mi_write(struct mft_inode *mi, int wait) |
|
{ |
|
struct MFT_REC *rec; |
|
int err; |
|
struct ntfs_sb_info *sbi; |
|
|
|
if (!mi->dirty) |
|
return 0; |
|
|
|
sbi = mi->sbi; |
|
rec = mi->mrec; |
|
|
|
err = ntfs_write_bh(sbi, &rec->rhdr, &mi->nb, wait); |
|
if (err) |
|
return err; |
|
|
|
if (mi->rno < sbi->mft.recs_mirr) |
|
sbi->flags |= NTFS_FLAGS_MFTMIRR; |
|
|
|
mi->dirty = false; |
|
|
|
return 0; |
|
} |
|
|
|
int mi_format_new(struct mft_inode *mi, struct ntfs_sb_info *sbi, CLST rno, |
|
__le16 flags, bool is_mft) |
|
{ |
|
int err; |
|
u16 seq = 1; |
|
struct MFT_REC *rec; |
|
u64 vbo = (u64)rno << sbi->record_bits; |
|
|
|
err = mi_init(mi, sbi, rno); |
|
if (err) |
|
return err; |
|
|
|
rec = mi->mrec; |
|
|
|
if (rno == MFT_REC_MFT) { |
|
; |
|
} else if (rno < MFT_REC_FREE) { |
|
seq = rno; |
|
} else if (rno >= sbi->mft.used) { |
|
; |
|
} else if (mi_read(mi, is_mft)) { |
|
; |
|
} else if (rec->rhdr.sign == NTFS_FILE_SIGNATURE) { |
|
/* Record is reused. Update its sequence number. */ |
|
seq = le16_to_cpu(rec->seq) + 1; |
|
if (!seq) |
|
seq = 1; |
|
} |
|
|
|
memcpy(rec, sbi->new_rec, sbi->record_size); |
|
|
|
rec->seq = cpu_to_le16(seq); |
|
rec->flags = RECORD_FLAG_IN_USE | flags; |
|
|
|
mi->dirty = true; |
|
|
|
if (!mi->nb.nbufs) { |
|
struct ntfs_inode *ni = sbi->mft.ni; |
|
bool lock = false; |
|
|
|
if (is_mounted(sbi) && !is_mft) { |
|
down_read(&ni->file.run_lock); |
|
lock = true; |
|
} |
|
|
|
err = ntfs_get_bh(sbi, &ni->file.run, vbo, sbi->record_size, |
|
&mi->nb); |
|
if (lock) |
|
up_read(&ni->file.run_lock); |
|
} |
|
|
|
return err; |
|
} |
|
|
|
/* |
|
* mi_mark_free - Mark record as unused and marks it as free in bitmap. |
|
*/ |
|
void mi_mark_free(struct mft_inode *mi) |
|
{ |
|
CLST rno = mi->rno; |
|
struct ntfs_sb_info *sbi = mi->sbi; |
|
|
|
if (rno >= MFT_REC_RESERVED && rno < MFT_REC_FREE) { |
|
ntfs_clear_mft_tail(sbi, rno, rno + 1); |
|
mi->dirty = false; |
|
return; |
|
} |
|
|
|
if (mi->mrec) { |
|
clear_rec_inuse(mi->mrec); |
|
mi->dirty = true; |
|
mi_write(mi, 0); |
|
} |
|
ntfs_mark_rec_free(sbi, rno); |
|
} |
|
|
|
/* |
|
* mi_insert_attr - Reserve space for new attribute. |
|
* |
|
* Return: Not full constructed attribute or NULL if not possible to create. |
|
*/ |
|
struct ATTRIB *mi_insert_attr(struct mft_inode *mi, enum ATTR_TYPE type, |
|
const __le16 *name, u8 name_len, u32 asize, |
|
u16 name_off) |
|
{ |
|
size_t tail; |
|
struct ATTRIB *attr; |
|
__le16 id; |
|
struct MFT_REC *rec = mi->mrec; |
|
struct ntfs_sb_info *sbi = mi->sbi; |
|
u32 used = le32_to_cpu(rec->used); |
|
const u16 *upcase = sbi->upcase; |
|
int diff; |
|
|
|
/* Can we insert mi attribute? */ |
|
if (used + asize > mi->sbi->record_size) |
|
return NULL; |
|
|
|
/* |
|
* Scan through the list of attributes to find the point |
|
* at which we should insert it. |
|
*/ |
|
attr = NULL; |
|
while ((attr = mi_enum_attr(mi, attr))) { |
|
diff = compare_attr(attr, type, name, name_len, upcase); |
|
if (diff > 0) |
|
break; |
|
if (diff < 0) |
|
continue; |
|
|
|
if (!is_attr_indexed(attr)) |
|
return NULL; |
|
break; |
|
} |
|
|
|
if (!attr) { |
|
tail = 8; /* Not used, just to suppress warning. */ |
|
attr = Add2Ptr(rec, used - 8); |
|
} else { |
|
tail = used - PtrOffset(rec, attr); |
|
} |
|
|
|
id = mi_new_attt_id(mi); |
|
|
|
memmove(Add2Ptr(attr, asize), attr, tail); |
|
memset(attr, 0, asize); |
|
|
|
attr->type = type; |
|
attr->size = cpu_to_le32(asize); |
|
attr->name_len = name_len; |
|
attr->name_off = cpu_to_le16(name_off); |
|
attr->id = id; |
|
|
|
memmove(Add2Ptr(attr, name_off), name, name_len * sizeof(short)); |
|
rec->used = cpu_to_le32(used + asize); |
|
|
|
mi->dirty = true; |
|
|
|
return attr; |
|
} |
|
|
|
/* |
|
* mi_remove_attr - Remove the attribute from record. |
|
* |
|
* NOTE: The source attr will point to next attribute. |
|
*/ |
|
bool mi_remove_attr(struct ntfs_inode *ni, struct mft_inode *mi, |
|
struct ATTRIB *attr) |
|
{ |
|
struct MFT_REC *rec = mi->mrec; |
|
u32 aoff = PtrOffset(rec, attr); |
|
u32 used = le32_to_cpu(rec->used); |
|
u32 asize = le32_to_cpu(attr->size); |
|
|
|
if (aoff + asize > used) |
|
return false; |
|
|
|
if (ni && is_attr_indexed(attr)) { |
|
le16_add_cpu(&ni->mi.mrec->hard_links, -1); |
|
ni->mi.dirty = true; |
|
} |
|
|
|
used -= asize; |
|
memmove(attr, Add2Ptr(attr, asize), used - aoff); |
|
rec->used = cpu_to_le32(used); |
|
mi->dirty = true; |
|
|
|
return true; |
|
} |
|
|
|
/* bytes = "new attribute size" - "old attribute size" */ |
|
bool mi_resize_attr(struct mft_inode *mi, struct ATTRIB *attr, int bytes) |
|
{ |
|
struct MFT_REC *rec = mi->mrec; |
|
u32 aoff = PtrOffset(rec, attr); |
|
u32 total, used = le32_to_cpu(rec->used); |
|
u32 nsize, asize = le32_to_cpu(attr->size); |
|
u32 rsize = le32_to_cpu(attr->res.data_size); |
|
int tail = (int)(used - aoff - asize); |
|
int dsize; |
|
char *next; |
|
|
|
if (tail < 0 || aoff >= used) |
|
return false; |
|
|
|
if (!bytes) |
|
return true; |
|
|
|
total = le32_to_cpu(rec->total); |
|
next = Add2Ptr(attr, asize); |
|
|
|
if (bytes > 0) { |
|
dsize = ALIGN(bytes, 8); |
|
if (used + dsize > total) |
|
return false; |
|
nsize = asize + dsize; |
|
/* Move tail */ |
|
memmove(next + dsize, next, tail); |
|
memset(next, 0, dsize); |
|
used += dsize; |
|
rsize += dsize; |
|
} else { |
|
dsize = ALIGN(-bytes, 8); |
|
if (dsize > asize) |
|
return false; |
|
nsize = asize - dsize; |
|
memmove(next - dsize, next, tail); |
|
used -= dsize; |
|
rsize -= dsize; |
|
} |
|
|
|
rec->used = cpu_to_le32(used); |
|
attr->size = cpu_to_le32(nsize); |
|
if (!attr->non_res) |
|
attr->res.data_size = cpu_to_le32(rsize); |
|
mi->dirty = true; |
|
|
|
return true; |
|
} |
|
|
|
int mi_pack_runs(struct mft_inode *mi, struct ATTRIB *attr, |
|
struct runs_tree *run, CLST len) |
|
{ |
|
int err = 0; |
|
struct ntfs_sb_info *sbi = mi->sbi; |
|
u32 new_run_size; |
|
CLST plen; |
|
struct MFT_REC *rec = mi->mrec; |
|
CLST svcn = le64_to_cpu(attr->nres.svcn); |
|
u32 used = le32_to_cpu(rec->used); |
|
u32 aoff = PtrOffset(rec, attr); |
|
u32 asize = le32_to_cpu(attr->size); |
|
char *next = Add2Ptr(attr, asize); |
|
u16 run_off = le16_to_cpu(attr->nres.run_off); |
|
u32 run_size = asize - run_off; |
|
u32 tail = used - aoff - asize; |
|
u32 dsize = sbi->record_size - used; |
|
|
|
/* Make a maximum gap in current record. */ |
|
memmove(next + dsize, next, tail); |
|
|
|
/* Pack as much as possible. */ |
|
err = run_pack(run, svcn, len, Add2Ptr(attr, run_off), run_size + dsize, |
|
&plen); |
|
if (err < 0) { |
|
memmove(next, next + dsize, tail); |
|
return err; |
|
} |
|
|
|
new_run_size = ALIGN(err, 8); |
|
|
|
memmove(next + new_run_size - run_size, next + dsize, tail); |
|
|
|
attr->size = cpu_to_le32(asize + new_run_size - run_size); |
|
attr->nres.evcn = cpu_to_le64(svcn + plen - 1); |
|
rec->used = cpu_to_le32(used + new_run_size - run_size); |
|
mi->dirty = true; |
|
|
|
return 0; |
|
}
|
|
|