| // 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; |
| } |
| |
| if (off + asize < off) { |
| /* overflow check */ |
| 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; |
| |
| t32 = sizeof(short) * attr->name_len; |
| if (t32 && le16_to_cpu(attr->name_off) + t32 > t16) |
| 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_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) |
| continue; |
| |
| if (!diff && !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; |
| } |
| |
| /* |
| * Pack runs in MFT record. |
| * If failed record is not changed. |
| */ |
| 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; |
| } |