mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-16 16:54:20 +08:00
14ab6d425e
-----BEGIN PGP SIGNATURE----- iHUEABYKAB0WIQRAhzRXHqcMeLMyaSiRxhvAZXjcogUCZTppYgAKCRCRxhvAZXjc okIHAP9anLz1QDyMLH12ASuHjgBc0Of3jcB6NB97IWGpL4O21gEA46ohaD+vcJuC YkBLU3lXqQ87nfu28ExFAzh10hG2jwM= =m4pB -----END PGP SIGNATURE----- Merge tag 'vfs-6.7.ctime' of gitolite.kernel.org:pub/scm/linux/kernel/git/vfs/vfs Pull vfs inode time accessor updates from Christian Brauner: "This finishes the conversion of all inode time fields to accessor functions as discussed on list. Changing timestamps manually as we used to do before is error prone. Using accessors function makes this robust. It does not contain the switch of the time fields to discrete 64 bit integers to replace struct timespec and free up space in struct inode. But after this, the switch can be trivially made and the patch should only affect the vfs if we decide to do it" * tag 'vfs-6.7.ctime' of gitolite.kernel.org:pub/scm/linux/kernel/git/vfs/vfs: (86 commits) fs: rename inode i_atime and i_mtime fields security: convert to new timestamp accessors selinux: convert to new timestamp accessors apparmor: convert to new timestamp accessors sunrpc: convert to new timestamp accessors mm: convert to new timestamp accessors bpf: convert to new timestamp accessors ipc: convert to new timestamp accessors linux: convert to new timestamp accessors zonefs: convert to new timestamp accessors xfs: convert to new timestamp accessors vboxsf: convert to new timestamp accessors ufs: convert to new timestamp accessors udf: convert to new timestamp accessors ubifs: convert to new timestamp accessors tracefs: convert to new timestamp accessors sysv: convert to new timestamp accessors squashfs: convert to new timestamp accessors server: convert to new timestamp accessors client: convert to new timestamp accessors ...
3387 lines
74 KiB
C
3387 lines
74 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
*
|
|
* Copyright (C) 2019-2021 Paragon Software GmbH, All rights reserved.
|
|
*
|
|
*/
|
|
|
|
#include <linux/fiemap.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/minmax.h>
|
|
#include <linux/vmalloc.h>
|
|
|
|
#include "debug.h"
|
|
#include "ntfs.h"
|
|
#include "ntfs_fs.h"
|
|
#ifdef CONFIG_NTFS3_LZX_XPRESS
|
|
#include "lib/lib.h"
|
|
#endif
|
|
|
|
static struct mft_inode *ni_ins_mi(struct ntfs_inode *ni, struct rb_root *tree,
|
|
CLST ino, struct rb_node *ins)
|
|
{
|
|
struct rb_node **p = &tree->rb_node;
|
|
struct rb_node *pr = NULL;
|
|
|
|
while (*p) {
|
|
struct mft_inode *mi;
|
|
|
|
pr = *p;
|
|
mi = rb_entry(pr, struct mft_inode, node);
|
|
if (mi->rno > ino)
|
|
p = &pr->rb_left;
|
|
else if (mi->rno < ino)
|
|
p = &pr->rb_right;
|
|
else
|
|
return mi;
|
|
}
|
|
|
|
if (!ins)
|
|
return NULL;
|
|
|
|
rb_link_node(ins, pr, p);
|
|
rb_insert_color(ins, tree);
|
|
return rb_entry(ins, struct mft_inode, node);
|
|
}
|
|
|
|
/*
|
|
* ni_find_mi - Find mft_inode by record number.
|
|
*/
|
|
static struct mft_inode *ni_find_mi(struct ntfs_inode *ni, CLST rno)
|
|
{
|
|
return ni_ins_mi(ni, &ni->mi_tree, rno, NULL);
|
|
}
|
|
|
|
/*
|
|
* ni_add_mi - Add new mft_inode into ntfs_inode.
|
|
*/
|
|
static void ni_add_mi(struct ntfs_inode *ni, struct mft_inode *mi)
|
|
{
|
|
ni_ins_mi(ni, &ni->mi_tree, mi->rno, &mi->node);
|
|
}
|
|
|
|
/*
|
|
* ni_remove_mi - Remove mft_inode from ntfs_inode.
|
|
*/
|
|
void ni_remove_mi(struct ntfs_inode *ni, struct mft_inode *mi)
|
|
{
|
|
rb_erase(&mi->node, &ni->mi_tree);
|
|
}
|
|
|
|
/*
|
|
* ni_std - Return: Pointer into std_info from primary record.
|
|
*/
|
|
struct ATTR_STD_INFO *ni_std(struct ntfs_inode *ni)
|
|
{
|
|
const struct ATTRIB *attr;
|
|
|
|
attr = mi_find_attr(&ni->mi, NULL, ATTR_STD, NULL, 0, NULL);
|
|
return attr ? resident_data_ex(attr, sizeof(struct ATTR_STD_INFO)) :
|
|
NULL;
|
|
}
|
|
|
|
/*
|
|
* ni_std5
|
|
*
|
|
* Return: Pointer into std_info from primary record.
|
|
*/
|
|
struct ATTR_STD_INFO5 *ni_std5(struct ntfs_inode *ni)
|
|
{
|
|
const struct ATTRIB *attr;
|
|
|
|
attr = mi_find_attr(&ni->mi, NULL, ATTR_STD, NULL, 0, NULL);
|
|
|
|
return attr ? resident_data_ex(attr, sizeof(struct ATTR_STD_INFO5)) :
|
|
NULL;
|
|
}
|
|
|
|
/*
|
|
* ni_clear - Clear resources allocated by ntfs_inode.
|
|
*/
|
|
void ni_clear(struct ntfs_inode *ni)
|
|
{
|
|
struct rb_node *node;
|
|
|
|
if (!ni->vfs_inode.i_nlink && ni->mi.mrec && is_rec_inuse(ni->mi.mrec))
|
|
ni_delete_all(ni);
|
|
|
|
al_destroy(ni);
|
|
|
|
for (node = rb_first(&ni->mi_tree); node;) {
|
|
struct rb_node *next = rb_next(node);
|
|
struct mft_inode *mi = rb_entry(node, struct mft_inode, node);
|
|
|
|
rb_erase(node, &ni->mi_tree);
|
|
mi_put(mi);
|
|
node = next;
|
|
}
|
|
|
|
/* Bad inode always has mode == S_IFREG. */
|
|
if (ni->ni_flags & NI_FLAG_DIR)
|
|
indx_clear(&ni->dir);
|
|
else {
|
|
run_close(&ni->file.run);
|
|
#ifdef CONFIG_NTFS3_LZX_XPRESS
|
|
if (ni->file.offs_page) {
|
|
/* On-demand allocated page for offsets. */
|
|
put_page(ni->file.offs_page);
|
|
ni->file.offs_page = NULL;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
mi_clear(&ni->mi);
|
|
}
|
|
|
|
/*
|
|
* ni_load_mi_ex - Find mft_inode by record number.
|
|
*/
|
|
int ni_load_mi_ex(struct ntfs_inode *ni, CLST rno, struct mft_inode **mi)
|
|
{
|
|
int err;
|
|
struct mft_inode *r;
|
|
|
|
r = ni_find_mi(ni, rno);
|
|
if (r)
|
|
goto out;
|
|
|
|
err = mi_get(ni->mi.sbi, rno, &r);
|
|
if (err)
|
|
return err;
|
|
|
|
ni_add_mi(ni, r);
|
|
|
|
out:
|
|
if (mi)
|
|
*mi = r;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* ni_load_mi - Load mft_inode corresponded list_entry.
|
|
*/
|
|
int ni_load_mi(struct ntfs_inode *ni, const struct ATTR_LIST_ENTRY *le,
|
|
struct mft_inode **mi)
|
|
{
|
|
CLST rno;
|
|
|
|
if (!le) {
|
|
*mi = &ni->mi;
|
|
return 0;
|
|
}
|
|
|
|
rno = ino_get(&le->ref);
|
|
if (rno == ni->mi.rno) {
|
|
*mi = &ni->mi;
|
|
return 0;
|
|
}
|
|
return ni_load_mi_ex(ni, rno, mi);
|
|
}
|
|
|
|
/*
|
|
* ni_find_attr
|
|
*
|
|
* Return: Attribute and record this attribute belongs to.
|
|
*/
|
|
struct ATTRIB *ni_find_attr(struct ntfs_inode *ni, struct ATTRIB *attr,
|
|
struct ATTR_LIST_ENTRY **le_o, enum ATTR_TYPE type,
|
|
const __le16 *name, u8 name_len, const CLST *vcn,
|
|
struct mft_inode **mi)
|
|
{
|
|
struct ATTR_LIST_ENTRY *le;
|
|
struct mft_inode *m;
|
|
|
|
if (!ni->attr_list.size ||
|
|
(!name_len && (type == ATTR_LIST || type == ATTR_STD))) {
|
|
if (le_o)
|
|
*le_o = NULL;
|
|
if (mi)
|
|
*mi = &ni->mi;
|
|
|
|
/* Look for required attribute in primary record. */
|
|
return mi_find_attr(&ni->mi, attr, type, name, name_len, NULL);
|
|
}
|
|
|
|
/* First look for list entry of required type. */
|
|
le = al_find_ex(ni, le_o ? *le_o : NULL, type, name, name_len, vcn);
|
|
if (!le)
|
|
return NULL;
|
|
|
|
if (le_o)
|
|
*le_o = le;
|
|
|
|
/* Load record that contains this attribute. */
|
|
if (ni_load_mi(ni, le, &m))
|
|
return NULL;
|
|
|
|
/* Look for required attribute. */
|
|
attr = mi_find_attr(m, NULL, type, name, name_len, &le->id);
|
|
|
|
if (!attr)
|
|
goto out;
|
|
|
|
if (!attr->non_res) {
|
|
if (vcn && *vcn)
|
|
goto out;
|
|
} else if (!vcn) {
|
|
if (attr->nres.svcn)
|
|
goto out;
|
|
} else if (le64_to_cpu(attr->nres.svcn) > *vcn ||
|
|
*vcn > le64_to_cpu(attr->nres.evcn)) {
|
|
goto out;
|
|
}
|
|
|
|
if (mi)
|
|
*mi = m;
|
|
return attr;
|
|
|
|
out:
|
|
ntfs_inode_err(&ni->vfs_inode, "failed to parse mft record");
|
|
ntfs_set_state(ni->mi.sbi, NTFS_DIRTY_ERROR);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* ni_enum_attr_ex - Enumerates attributes in ntfs_inode.
|
|
*/
|
|
struct ATTRIB *ni_enum_attr_ex(struct ntfs_inode *ni, struct ATTRIB *attr,
|
|
struct ATTR_LIST_ENTRY **le,
|
|
struct mft_inode **mi)
|
|
{
|
|
struct mft_inode *mi2;
|
|
struct ATTR_LIST_ENTRY *le2;
|
|
|
|
/* Do we have an attribute list? */
|
|
if (!ni->attr_list.size) {
|
|
*le = NULL;
|
|
if (mi)
|
|
*mi = &ni->mi;
|
|
/* Enum attributes in primary record. */
|
|
return mi_enum_attr(&ni->mi, attr);
|
|
}
|
|
|
|
/* Get next list entry. */
|
|
le2 = *le = al_enumerate(ni, attr ? *le : NULL);
|
|
if (!le2)
|
|
return NULL;
|
|
|
|
/* Load record that contains the required attribute. */
|
|
if (ni_load_mi(ni, le2, &mi2))
|
|
return NULL;
|
|
|
|
if (mi)
|
|
*mi = mi2;
|
|
|
|
/* Find attribute in loaded record. */
|
|
return rec_find_attr_le(mi2, le2);
|
|
}
|
|
|
|
/*
|
|
* ni_load_attr - Load attribute that contains given VCN.
|
|
*/
|
|
struct ATTRIB *ni_load_attr(struct ntfs_inode *ni, enum ATTR_TYPE type,
|
|
const __le16 *name, u8 name_len, CLST vcn,
|
|
struct mft_inode **pmi)
|
|
{
|
|
struct ATTR_LIST_ENTRY *le;
|
|
struct ATTRIB *attr;
|
|
struct mft_inode *mi;
|
|
struct ATTR_LIST_ENTRY *next;
|
|
|
|
if (!ni->attr_list.size) {
|
|
if (pmi)
|
|
*pmi = &ni->mi;
|
|
return mi_find_attr(&ni->mi, NULL, type, name, name_len, NULL);
|
|
}
|
|
|
|
le = al_find_ex(ni, NULL, type, name, name_len, NULL);
|
|
if (!le)
|
|
return NULL;
|
|
|
|
/*
|
|
* Unfortunately ATTR_LIST_ENTRY contains only start VCN.
|
|
* So to find the ATTRIB segment that contains 'vcn' we should
|
|
* enumerate some entries.
|
|
*/
|
|
if (vcn) {
|
|
for (;; le = next) {
|
|
next = al_find_ex(ni, le, type, name, name_len, NULL);
|
|
if (!next || le64_to_cpu(next->vcn) > vcn)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ni_load_mi(ni, le, &mi))
|
|
return NULL;
|
|
|
|
if (pmi)
|
|
*pmi = mi;
|
|
|
|
attr = mi_find_attr(mi, NULL, type, name, name_len, &le->id);
|
|
if (!attr)
|
|
return NULL;
|
|
|
|
if (!attr->non_res)
|
|
return attr;
|
|
|
|
if (le64_to_cpu(attr->nres.svcn) <= vcn &&
|
|
vcn <= le64_to_cpu(attr->nres.evcn))
|
|
return attr;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* ni_load_all_mi - Load all subrecords.
|
|
*/
|
|
int ni_load_all_mi(struct ntfs_inode *ni)
|
|
{
|
|
int err;
|
|
struct ATTR_LIST_ENTRY *le;
|
|
|
|
if (!ni->attr_list.size)
|
|
return 0;
|
|
|
|
le = NULL;
|
|
|
|
while ((le = al_enumerate(ni, le))) {
|
|
CLST rno = ino_get(&le->ref);
|
|
|
|
if (rno == ni->mi.rno)
|
|
continue;
|
|
|
|
err = ni_load_mi_ex(ni, rno, NULL);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* ni_add_subrecord - Allocate + format + attach a new subrecord.
|
|
*/
|
|
bool ni_add_subrecord(struct ntfs_inode *ni, CLST rno, struct mft_inode **mi)
|
|
{
|
|
struct mft_inode *m;
|
|
|
|
m = kzalloc(sizeof(struct mft_inode), GFP_NOFS);
|
|
if (!m)
|
|
return false;
|
|
|
|
if (mi_format_new(m, ni->mi.sbi, rno, 0, ni->mi.rno == MFT_REC_MFT)) {
|
|
mi_put(m);
|
|
return false;
|
|
}
|
|
|
|
mi_get_ref(&ni->mi, &m->mrec->parent_ref);
|
|
|
|
ni_add_mi(ni, m);
|
|
*mi = m;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* ni_remove_attr - Remove all attributes for the given type/name/id.
|
|
*/
|
|
int ni_remove_attr(struct ntfs_inode *ni, enum ATTR_TYPE type,
|
|
const __le16 *name, u8 name_len, bool base_only,
|
|
const __le16 *id)
|
|
{
|
|
int err;
|
|
struct ATTRIB *attr;
|
|
struct ATTR_LIST_ENTRY *le;
|
|
struct mft_inode *mi;
|
|
u32 type_in;
|
|
int diff;
|
|
|
|
if (base_only || type == ATTR_LIST || !ni->attr_list.size) {
|
|
attr = mi_find_attr(&ni->mi, NULL, type, name, name_len, id);
|
|
if (!attr)
|
|
return -ENOENT;
|
|
|
|
mi_remove_attr(ni, &ni->mi, attr);
|
|
return 0;
|
|
}
|
|
|
|
type_in = le32_to_cpu(type);
|
|
le = NULL;
|
|
|
|
for (;;) {
|
|
le = al_enumerate(ni, le);
|
|
if (!le)
|
|
return 0;
|
|
|
|
next_le2:
|
|
diff = le32_to_cpu(le->type) - type_in;
|
|
if (diff < 0)
|
|
continue;
|
|
|
|
if (diff > 0)
|
|
return 0;
|
|
|
|
if (le->name_len != name_len)
|
|
continue;
|
|
|
|
if (name_len &&
|
|
memcmp(le_name(le), name, name_len * sizeof(short)))
|
|
continue;
|
|
|
|
if (id && le->id != *id)
|
|
continue;
|
|
err = ni_load_mi(ni, le, &mi);
|
|
if (err)
|
|
return err;
|
|
|
|
al_remove_le(ni, le);
|
|
|
|
attr = mi_find_attr(mi, NULL, type, name, name_len, id);
|
|
if (!attr)
|
|
return -ENOENT;
|
|
|
|
mi_remove_attr(ni, mi, attr);
|
|
|
|
if (PtrOffset(ni->attr_list.le, le) >= ni->attr_list.size)
|
|
return 0;
|
|
goto next_le2;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ni_ins_new_attr - Insert the attribute into record.
|
|
*
|
|
* Return: Not full constructed attribute or NULL if not possible to create.
|
|
*/
|
|
static struct ATTRIB *
|
|
ni_ins_new_attr(struct ntfs_inode *ni, struct mft_inode *mi,
|
|
struct ATTR_LIST_ENTRY *le, enum ATTR_TYPE type,
|
|
const __le16 *name, u8 name_len, u32 asize, u16 name_off,
|
|
CLST svcn, struct ATTR_LIST_ENTRY **ins_le)
|
|
{
|
|
int err;
|
|
struct ATTRIB *attr;
|
|
bool le_added = false;
|
|
struct MFT_REF ref;
|
|
|
|
mi_get_ref(mi, &ref);
|
|
|
|
if (type != ATTR_LIST && !le && ni->attr_list.size) {
|
|
err = al_add_le(ni, type, name, name_len, svcn, cpu_to_le16(-1),
|
|
&ref, &le);
|
|
if (err) {
|
|
/* No memory or no space. */
|
|
return ERR_PTR(err);
|
|
}
|
|
le_added = true;
|
|
|
|
/*
|
|
* al_add_le -> attr_set_size (list) -> ni_expand_list
|
|
* which moves some attributes out of primary record
|
|
* this means that name may point into moved memory
|
|
* reinit 'name' from le.
|
|
*/
|
|
name = le->name;
|
|
}
|
|
|
|
attr = mi_insert_attr(mi, type, name, name_len, asize, name_off);
|
|
if (!attr) {
|
|
if (le_added)
|
|
al_remove_le(ni, le);
|
|
return NULL;
|
|
}
|
|
|
|
if (type == ATTR_LIST) {
|
|
/* Attr list is not in list entry array. */
|
|
goto out;
|
|
}
|
|
|
|
if (!le)
|
|
goto out;
|
|
|
|
/* Update ATTRIB Id and record reference. */
|
|
le->id = attr->id;
|
|
ni->attr_list.dirty = true;
|
|
le->ref = ref;
|
|
|
|
out:
|
|
if (ins_le)
|
|
*ins_le = le;
|
|
return attr;
|
|
}
|
|
|
|
/*
|
|
* ni_repack
|
|
*
|
|
* Random write access to sparsed or compressed file may result to
|
|
* not optimized packed runs.
|
|
* Here is the place to optimize it.
|
|
*/
|
|
static int ni_repack(struct ntfs_inode *ni)
|
|
{
|
|
#if 1
|
|
return 0;
|
|
#else
|
|
int err = 0;
|
|
struct ntfs_sb_info *sbi = ni->mi.sbi;
|
|
struct mft_inode *mi, *mi_p = NULL;
|
|
struct ATTRIB *attr = NULL, *attr_p;
|
|
struct ATTR_LIST_ENTRY *le = NULL, *le_p;
|
|
CLST alloc = 0;
|
|
u8 cluster_bits = sbi->cluster_bits;
|
|
CLST svcn, evcn = 0, svcn_p, evcn_p, next_svcn;
|
|
u32 roff, rs = sbi->record_size;
|
|
struct runs_tree run;
|
|
|
|
run_init(&run);
|
|
|
|
while ((attr = ni_enum_attr_ex(ni, attr, &le, &mi))) {
|
|
if (!attr->non_res)
|
|
continue;
|
|
|
|
svcn = le64_to_cpu(attr->nres.svcn);
|
|
if (svcn != le64_to_cpu(le->vcn)) {
|
|
err = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
if (!svcn) {
|
|
alloc = le64_to_cpu(attr->nres.alloc_size) >>
|
|
cluster_bits;
|
|
mi_p = NULL;
|
|
} else if (svcn != evcn + 1) {
|
|
err = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
evcn = le64_to_cpu(attr->nres.evcn);
|
|
|
|
if (svcn > evcn + 1) {
|
|
err = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
if (!mi_p) {
|
|
/* Do not try if not enough free space. */
|
|
if (le32_to_cpu(mi->mrec->used) + 8 >= rs)
|
|
continue;
|
|
|
|
/* Do not try if last attribute segment. */
|
|
if (evcn + 1 == alloc)
|
|
continue;
|
|
run_close(&run);
|
|
}
|
|
|
|
roff = le16_to_cpu(attr->nres.run_off);
|
|
|
|
if (roff > le32_to_cpu(attr->size)) {
|
|
err = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
err = run_unpack(&run, sbi, ni->mi.rno, svcn, evcn, svcn,
|
|
Add2Ptr(attr, roff),
|
|
le32_to_cpu(attr->size) - roff);
|
|
if (err < 0)
|
|
break;
|
|
|
|
if (!mi_p) {
|
|
mi_p = mi;
|
|
attr_p = attr;
|
|
svcn_p = svcn;
|
|
evcn_p = evcn;
|
|
le_p = le;
|
|
err = 0;
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Run contains data from two records: mi_p and mi
|
|
* Try to pack in one.
|
|
*/
|
|
err = mi_pack_runs(mi_p, attr_p, &run, evcn + 1 - svcn_p);
|
|
if (err)
|
|
break;
|
|
|
|
next_svcn = le64_to_cpu(attr_p->nres.evcn) + 1;
|
|
|
|
if (next_svcn >= evcn + 1) {
|
|
/* We can remove this attribute segment. */
|
|
al_remove_le(ni, le);
|
|
mi_remove_attr(NULL, mi, attr);
|
|
le = le_p;
|
|
continue;
|
|
}
|
|
|
|
attr->nres.svcn = le->vcn = cpu_to_le64(next_svcn);
|
|
mi->dirty = true;
|
|
ni->attr_list.dirty = true;
|
|
|
|
if (evcn + 1 == alloc) {
|
|
err = mi_pack_runs(mi, attr, &run,
|
|
evcn + 1 - next_svcn);
|
|
if (err)
|
|
break;
|
|
mi_p = NULL;
|
|
} else {
|
|
mi_p = mi;
|
|
attr_p = attr;
|
|
svcn_p = next_svcn;
|
|
evcn_p = evcn;
|
|
le_p = le;
|
|
run_truncate_head(&run, next_svcn);
|
|
}
|
|
}
|
|
|
|
if (err) {
|
|
ntfs_inode_warn(&ni->vfs_inode, "repack problem");
|
|
ntfs_set_state(sbi, NTFS_DIRTY_ERROR);
|
|
|
|
/* Pack loaded but not packed runs. */
|
|
if (mi_p)
|
|
mi_pack_runs(mi_p, attr_p, &run, evcn_p + 1 - svcn_p);
|
|
}
|
|
|
|
run_close(&run);
|
|
return err;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* ni_try_remove_attr_list
|
|
*
|
|
* Can we remove attribute list?
|
|
* Check the case when primary record contains enough space for all attributes.
|
|
*/
|
|
static int ni_try_remove_attr_list(struct ntfs_inode *ni)
|
|
{
|
|
int err = 0;
|
|
struct ntfs_sb_info *sbi = ni->mi.sbi;
|
|
struct ATTRIB *attr, *attr_list, *attr_ins;
|
|
struct ATTR_LIST_ENTRY *le;
|
|
struct mft_inode *mi;
|
|
u32 asize, free;
|
|
struct MFT_REF ref;
|
|
struct MFT_REC *mrec;
|
|
__le16 id;
|
|
|
|
if (!ni->attr_list.dirty)
|
|
return 0;
|
|
|
|
err = ni_repack(ni);
|
|
if (err)
|
|
return err;
|
|
|
|
attr_list = mi_find_attr(&ni->mi, NULL, ATTR_LIST, NULL, 0, NULL);
|
|
if (!attr_list)
|
|
return 0;
|
|
|
|
asize = le32_to_cpu(attr_list->size);
|
|
|
|
/* Free space in primary record without attribute list. */
|
|
free = sbi->record_size - le32_to_cpu(ni->mi.mrec->used) + asize;
|
|
mi_get_ref(&ni->mi, &ref);
|
|
|
|
le = NULL;
|
|
while ((le = al_enumerate(ni, le))) {
|
|
if (!memcmp(&le->ref, &ref, sizeof(ref)))
|
|
continue;
|
|
|
|
if (le->vcn)
|
|
return 0;
|
|
|
|
mi = ni_find_mi(ni, ino_get(&le->ref));
|
|
if (!mi)
|
|
return 0;
|
|
|
|
attr = mi_find_attr(mi, NULL, le->type, le_name(le),
|
|
le->name_len, &le->id);
|
|
if (!attr)
|
|
return 0;
|
|
|
|
asize = le32_to_cpu(attr->size);
|
|
if (asize > free)
|
|
return 0;
|
|
|
|
free -= asize;
|
|
}
|
|
|
|
/* Make a copy of primary record to restore if error. */
|
|
mrec = kmemdup(ni->mi.mrec, sbi->record_size, GFP_NOFS);
|
|
if (!mrec)
|
|
return 0; /* Not critical. */
|
|
|
|
/* It seems that attribute list can be removed from primary record. */
|
|
mi_remove_attr(NULL, &ni->mi, attr_list);
|
|
|
|
/*
|
|
* Repeat the cycle above and copy all attributes to primary record.
|
|
* Do not remove original attributes from subrecords!
|
|
* It should be success!
|
|
*/
|
|
le = NULL;
|
|
while ((le = al_enumerate(ni, le))) {
|
|
if (!memcmp(&le->ref, &ref, sizeof(ref)))
|
|
continue;
|
|
|
|
mi = ni_find_mi(ni, ino_get(&le->ref));
|
|
if (!mi) {
|
|
/* Should never happened, 'cause already checked. */
|
|
goto out;
|
|
}
|
|
|
|
attr = mi_find_attr(mi, NULL, le->type, le_name(le),
|
|
le->name_len, &le->id);
|
|
if (!attr) {
|
|
/* Should never happened, 'cause already checked. */
|
|
goto out;
|
|
}
|
|
asize = le32_to_cpu(attr->size);
|
|
|
|
/* Insert into primary record. */
|
|
attr_ins = mi_insert_attr(&ni->mi, le->type, le_name(le),
|
|
le->name_len, asize,
|
|
le16_to_cpu(attr->name_off));
|
|
if (!attr_ins) {
|
|
/*
|
|
* No space in primary record (already checked).
|
|
*/
|
|
goto out;
|
|
}
|
|
|
|
/* Copy all except id. */
|
|
id = attr_ins->id;
|
|
memcpy(attr_ins, attr, asize);
|
|
attr_ins->id = id;
|
|
}
|
|
|
|
/*
|
|
* Repeat the cycle above and remove all attributes from subrecords.
|
|
*/
|
|
le = NULL;
|
|
while ((le = al_enumerate(ni, le))) {
|
|
if (!memcmp(&le->ref, &ref, sizeof(ref)))
|
|
continue;
|
|
|
|
mi = ni_find_mi(ni, ino_get(&le->ref));
|
|
if (!mi)
|
|
continue;
|
|
|
|
attr = mi_find_attr(mi, NULL, le->type, le_name(le),
|
|
le->name_len, &le->id);
|
|
if (!attr)
|
|
continue;
|
|
|
|
/* Remove from original record. */
|
|
mi_remove_attr(NULL, mi, attr);
|
|
}
|
|
|
|
run_deallocate(sbi, &ni->attr_list.run, true);
|
|
run_close(&ni->attr_list.run);
|
|
ni->attr_list.size = 0;
|
|
kfree(ni->attr_list.le);
|
|
ni->attr_list.le = NULL;
|
|
ni->attr_list.dirty = false;
|
|
|
|
kfree(mrec);
|
|
return 0;
|
|
out:
|
|
/* Restore primary record. */
|
|
swap(mrec, ni->mi.mrec);
|
|
kfree(mrec);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* ni_create_attr_list - Generates an attribute list for this primary record.
|
|
*/
|
|
int ni_create_attr_list(struct ntfs_inode *ni)
|
|
{
|
|
struct ntfs_sb_info *sbi = ni->mi.sbi;
|
|
int err;
|
|
u32 lsize;
|
|
struct ATTRIB *attr;
|
|
struct ATTRIB *arr_move[7];
|
|
struct ATTR_LIST_ENTRY *le, *le_b[7];
|
|
struct MFT_REC *rec;
|
|
bool is_mft;
|
|
CLST rno = 0;
|
|
struct mft_inode *mi;
|
|
u32 free_b, nb, to_free, rs;
|
|
u16 sz;
|
|
|
|
is_mft = ni->mi.rno == MFT_REC_MFT;
|
|
rec = ni->mi.mrec;
|
|
rs = sbi->record_size;
|
|
|
|
/*
|
|
* Skip estimating exact memory requirement.
|
|
* Looks like one record_size is always enough.
|
|
*/
|
|
le = kmalloc(al_aligned(rs), GFP_NOFS);
|
|
if (!le)
|
|
return -ENOMEM;
|
|
|
|
mi_get_ref(&ni->mi, &le->ref);
|
|
ni->attr_list.le = le;
|
|
|
|
attr = NULL;
|
|
nb = 0;
|
|
free_b = 0;
|
|
attr = NULL;
|
|
|
|
for (; (attr = mi_enum_attr(&ni->mi, attr)); le = Add2Ptr(le, sz)) {
|
|
sz = le_size(attr->name_len);
|
|
le->type = attr->type;
|
|
le->size = cpu_to_le16(sz);
|
|
le->name_len = attr->name_len;
|
|
le->name_off = offsetof(struct ATTR_LIST_ENTRY, name);
|
|
le->vcn = 0;
|
|
if (le != ni->attr_list.le)
|
|
le->ref = ni->attr_list.le->ref;
|
|
le->id = attr->id;
|
|
|
|
if (attr->name_len)
|
|
memcpy(le->name, attr_name(attr),
|
|
sizeof(short) * attr->name_len);
|
|
else if (attr->type == ATTR_STD)
|
|
continue;
|
|
else if (attr->type == ATTR_LIST)
|
|
continue;
|
|
else if (is_mft && attr->type == ATTR_DATA)
|
|
continue;
|
|
|
|
if (!nb || nb < ARRAY_SIZE(arr_move)) {
|
|
le_b[nb] = le;
|
|
arr_move[nb++] = attr;
|
|
free_b += le32_to_cpu(attr->size);
|
|
}
|
|
}
|
|
|
|
lsize = PtrOffset(ni->attr_list.le, le);
|
|
ni->attr_list.size = lsize;
|
|
|
|
to_free = le32_to_cpu(rec->used) + lsize + SIZEOF_RESIDENT;
|
|
if (to_free <= rs) {
|
|
to_free = 0;
|
|
} else {
|
|
to_free -= rs;
|
|
|
|
if (to_free > free_b) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
/* Allocate child MFT. */
|
|
err = ntfs_look_free_mft(sbi, &rno, is_mft, ni, &mi);
|
|
if (err)
|
|
goto out;
|
|
|
|
err = -EINVAL;
|
|
/* Call mi_remove_attr() in reverse order to keep pointers 'arr_move' valid. */
|
|
while (to_free > 0) {
|
|
struct ATTRIB *b = arr_move[--nb];
|
|
u32 asize = le32_to_cpu(b->size);
|
|
u16 name_off = le16_to_cpu(b->name_off);
|
|
|
|
attr = mi_insert_attr(mi, b->type, Add2Ptr(b, name_off),
|
|
b->name_len, asize, name_off);
|
|
if (!attr)
|
|
goto out;
|
|
|
|
mi_get_ref(mi, &le_b[nb]->ref);
|
|
le_b[nb]->id = attr->id;
|
|
|
|
/* Copy all except id. */
|
|
memcpy(attr, b, asize);
|
|
attr->id = le_b[nb]->id;
|
|
|
|
/* Remove from primary record. */
|
|
if (!mi_remove_attr(NULL, &ni->mi, b))
|
|
goto out;
|
|
|
|
if (to_free <= asize)
|
|
break;
|
|
to_free -= asize;
|
|
if (!nb)
|
|
goto out;
|
|
}
|
|
|
|
attr = mi_insert_attr(&ni->mi, ATTR_LIST, NULL, 0,
|
|
lsize + SIZEOF_RESIDENT, SIZEOF_RESIDENT);
|
|
if (!attr)
|
|
goto out;
|
|
|
|
attr->non_res = 0;
|
|
attr->flags = 0;
|
|
attr->res.data_size = cpu_to_le32(lsize);
|
|
attr->res.data_off = SIZEOF_RESIDENT_LE;
|
|
attr->res.flags = 0;
|
|
attr->res.res = 0;
|
|
|
|
memcpy(resident_data_ex(attr, lsize), ni->attr_list.le, lsize);
|
|
|
|
ni->attr_list.dirty = false;
|
|
|
|
mark_inode_dirty(&ni->vfs_inode);
|
|
return 0;
|
|
|
|
out:
|
|
kfree(ni->attr_list.le);
|
|
ni->attr_list.le = NULL;
|
|
ni->attr_list.size = 0;
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* ni_ins_attr_ext - Add an external attribute to the ntfs_inode.
|
|
*/
|
|
static int ni_ins_attr_ext(struct ntfs_inode *ni, struct ATTR_LIST_ENTRY *le,
|
|
enum ATTR_TYPE type, const __le16 *name, u8 name_len,
|
|
u32 asize, CLST svcn, u16 name_off, bool force_ext,
|
|
struct ATTRIB **ins_attr, struct mft_inode **ins_mi,
|
|
struct ATTR_LIST_ENTRY **ins_le)
|
|
{
|
|
struct ATTRIB *attr;
|
|
struct mft_inode *mi;
|
|
CLST rno;
|
|
u64 vbo;
|
|
struct rb_node *node;
|
|
int err;
|
|
bool is_mft, is_mft_data;
|
|
struct ntfs_sb_info *sbi = ni->mi.sbi;
|
|
|
|
is_mft = ni->mi.rno == MFT_REC_MFT;
|
|
is_mft_data = is_mft && type == ATTR_DATA && !name_len;
|
|
|
|
if (asize > sbi->max_bytes_per_attr) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Standard information and attr_list cannot be made external.
|
|
* The Log File cannot have any external attributes.
|
|
*/
|
|
if (type == ATTR_STD || type == ATTR_LIST ||
|
|
ni->mi.rno == MFT_REC_LOG) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
/* Create attribute list if it is not already existed. */
|
|
if (!ni->attr_list.size) {
|
|
err = ni_create_attr_list(ni);
|
|
if (err)
|
|
goto out;
|
|
}
|
|
|
|
vbo = is_mft_data ? ((u64)svcn << sbi->cluster_bits) : 0;
|
|
|
|
if (force_ext)
|
|
goto insert_ext;
|
|
|
|
/* Load all subrecords into memory. */
|
|
err = ni_load_all_mi(ni);
|
|
if (err)
|
|
goto out;
|
|
|
|
/* Check each of loaded subrecord. */
|
|
for (node = rb_first(&ni->mi_tree); node; node = rb_next(node)) {
|
|
mi = rb_entry(node, struct mft_inode, node);
|
|
|
|
if (is_mft_data &&
|
|
(mi_enum_attr(mi, NULL) ||
|
|
vbo <= ((u64)mi->rno << sbi->record_bits))) {
|
|
/* We can't accept this record 'cause MFT's bootstrapping. */
|
|
continue;
|
|
}
|
|
if (is_mft &&
|
|
mi_find_attr(mi, NULL, ATTR_DATA, NULL, 0, NULL)) {
|
|
/*
|
|
* This child record already has a ATTR_DATA.
|
|
* So it can't accept any other records.
|
|
*/
|
|
continue;
|
|
}
|
|
|
|
if ((type != ATTR_NAME || name_len) &&
|
|
mi_find_attr(mi, NULL, type, name, name_len, NULL)) {
|
|
/* Only indexed attributes can share same record. */
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Do not try to insert this attribute
|
|
* if there is no room in record.
|
|
*/
|
|
if (le32_to_cpu(mi->mrec->used) + asize > sbi->record_size)
|
|
continue;
|
|
|
|
/* Try to insert attribute into this subrecord. */
|
|
attr = ni_ins_new_attr(ni, mi, le, type, name, name_len, asize,
|
|
name_off, svcn, ins_le);
|
|
if (!attr)
|
|
continue;
|
|
if (IS_ERR(attr))
|
|
return PTR_ERR(attr);
|
|
|
|
if (ins_attr)
|
|
*ins_attr = attr;
|
|
if (ins_mi)
|
|
*ins_mi = mi;
|
|
return 0;
|
|
}
|
|
|
|
insert_ext:
|
|
/* We have to allocate a new child subrecord. */
|
|
err = ntfs_look_free_mft(sbi, &rno, is_mft_data, ni, &mi);
|
|
if (err)
|
|
goto out;
|
|
|
|
if (is_mft_data && vbo <= ((u64)rno << sbi->record_bits)) {
|
|
err = -EINVAL;
|
|
goto out1;
|
|
}
|
|
|
|
attr = ni_ins_new_attr(ni, mi, le, type, name, name_len, asize,
|
|
name_off, svcn, ins_le);
|
|
if (!attr) {
|
|
err = -EINVAL;
|
|
goto out2;
|
|
}
|
|
|
|
if (IS_ERR(attr)) {
|
|
err = PTR_ERR(attr);
|
|
goto out2;
|
|
}
|
|
|
|
if (ins_attr)
|
|
*ins_attr = attr;
|
|
if (ins_mi)
|
|
*ins_mi = mi;
|
|
|
|
return 0;
|
|
|
|
out2:
|
|
ni_remove_mi(ni, mi);
|
|
mi_put(mi);
|
|
|
|
out1:
|
|
ntfs_mark_rec_free(sbi, rno, is_mft);
|
|
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* ni_insert_attr - Insert an attribute into the file.
|
|
*
|
|
* If the primary record has room, it will just insert the attribute.
|
|
* If not, it may make the attribute external.
|
|
* For $MFT::Data it may make room for the attribute by
|
|
* making other attributes external.
|
|
*
|
|
* NOTE:
|
|
* The ATTR_LIST and ATTR_STD cannot be made external.
|
|
* This function does not fill new attribute full.
|
|
* It only fills 'size'/'type'/'id'/'name_len' fields.
|
|
*/
|
|
static int ni_insert_attr(struct ntfs_inode *ni, enum ATTR_TYPE type,
|
|
const __le16 *name, u8 name_len, u32 asize,
|
|
u16 name_off, CLST svcn, struct ATTRIB **ins_attr,
|
|
struct mft_inode **ins_mi,
|
|
struct ATTR_LIST_ENTRY **ins_le)
|
|
{
|
|
struct ntfs_sb_info *sbi = ni->mi.sbi;
|
|
int err;
|
|
struct ATTRIB *attr, *eattr;
|
|
struct MFT_REC *rec;
|
|
bool is_mft;
|
|
struct ATTR_LIST_ENTRY *le;
|
|
u32 list_reserve, max_free, free, used, t32;
|
|
__le16 id;
|
|
u16 t16;
|
|
|
|
is_mft = ni->mi.rno == MFT_REC_MFT;
|
|
rec = ni->mi.mrec;
|
|
|
|
list_reserve = SIZEOF_NONRESIDENT + 3 * (1 + 2 * sizeof(u32));
|
|
used = le32_to_cpu(rec->used);
|
|
free = sbi->record_size - used;
|
|
|
|
if (is_mft && type != ATTR_LIST) {
|
|
/* Reserve space for the ATTRIB list. */
|
|
if (free < list_reserve)
|
|
free = 0;
|
|
else
|
|
free -= list_reserve;
|
|
}
|
|
|
|
if (asize <= free) {
|
|
attr = ni_ins_new_attr(ni, &ni->mi, NULL, type, name, name_len,
|
|
asize, name_off, svcn, ins_le);
|
|
if (IS_ERR(attr)) {
|
|
err = PTR_ERR(attr);
|
|
goto out;
|
|
}
|
|
|
|
if (attr) {
|
|
if (ins_attr)
|
|
*ins_attr = attr;
|
|
if (ins_mi)
|
|
*ins_mi = &ni->mi;
|
|
err = 0;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (!is_mft || type != ATTR_DATA || svcn) {
|
|
/* This ATTRIB will be external. */
|
|
err = ni_ins_attr_ext(ni, NULL, type, name, name_len, asize,
|
|
svcn, name_off, false, ins_attr, ins_mi,
|
|
ins_le);
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Here we have: "is_mft && type == ATTR_DATA && !svcn"
|
|
*
|
|
* The first chunk of the $MFT::Data ATTRIB must be the base record.
|
|
* Evict as many other attributes as possible.
|
|
*/
|
|
max_free = free;
|
|
|
|
/* Estimate the result of moving all possible attributes away. */
|
|
attr = NULL;
|
|
|
|
while ((attr = mi_enum_attr(&ni->mi, attr))) {
|
|
if (attr->type == ATTR_STD)
|
|
continue;
|
|
if (attr->type == ATTR_LIST)
|
|
continue;
|
|
max_free += le32_to_cpu(attr->size);
|
|
}
|
|
|
|
if (max_free < asize + list_reserve) {
|
|
/* Impossible to insert this attribute into primary record. */
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
/* Start real attribute moving. */
|
|
attr = NULL;
|
|
|
|
for (;;) {
|
|
attr = mi_enum_attr(&ni->mi, attr);
|
|
if (!attr) {
|
|
/* We should never be here 'cause we have already check this case. */
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
/* Skip attributes that MUST be primary record. */
|
|
if (attr->type == ATTR_STD || attr->type == ATTR_LIST)
|
|
continue;
|
|
|
|
le = NULL;
|
|
if (ni->attr_list.size) {
|
|
le = al_find_le(ni, NULL, attr);
|
|
if (!le) {
|
|
/* Really this is a serious bug. */
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
t32 = le32_to_cpu(attr->size);
|
|
t16 = le16_to_cpu(attr->name_off);
|
|
err = ni_ins_attr_ext(ni, le, attr->type, Add2Ptr(attr, t16),
|
|
attr->name_len, t32, attr_svcn(attr), t16,
|
|
false, &eattr, NULL, NULL);
|
|
if (err)
|
|
return err;
|
|
|
|
id = eattr->id;
|
|
memcpy(eattr, attr, t32);
|
|
eattr->id = id;
|
|
|
|
/* Remove from primary record. */
|
|
mi_remove_attr(NULL, &ni->mi, attr);
|
|
|
|
/* attr now points to next attribute. */
|
|
if (attr->type == ATTR_END)
|
|
goto out;
|
|
}
|
|
while (asize + list_reserve > sbi->record_size - le32_to_cpu(rec->used))
|
|
;
|
|
|
|
attr = ni_ins_new_attr(ni, &ni->mi, NULL, type, name, name_len, asize,
|
|
name_off, svcn, ins_le);
|
|
if (!attr) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if (IS_ERR(attr)) {
|
|
err = PTR_ERR(attr);
|
|
goto out;
|
|
}
|
|
|
|
if (ins_attr)
|
|
*ins_attr = attr;
|
|
if (ins_mi)
|
|
*ins_mi = &ni->mi;
|
|
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
/* ni_expand_mft_list - Split ATTR_DATA of $MFT. */
|
|
static int ni_expand_mft_list(struct ntfs_inode *ni)
|
|
{
|
|
int err = 0;
|
|
struct runs_tree *run = &ni->file.run;
|
|
u32 asize, run_size, done = 0;
|
|
struct ATTRIB *attr;
|
|
struct rb_node *node;
|
|
CLST mft_min, mft_new, svcn, evcn, plen;
|
|
struct mft_inode *mi, *mi_min, *mi_new;
|
|
struct ntfs_sb_info *sbi = ni->mi.sbi;
|
|
|
|
/* Find the nearest MFT. */
|
|
mft_min = 0;
|
|
mft_new = 0;
|
|
mi_min = NULL;
|
|
|
|
for (node = rb_first(&ni->mi_tree); node; node = rb_next(node)) {
|
|
mi = rb_entry(node, struct mft_inode, node);
|
|
|
|
attr = mi_enum_attr(mi, NULL);
|
|
|
|
if (!attr) {
|
|
mft_min = mi->rno;
|
|
mi_min = mi;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ntfs_look_free_mft(sbi, &mft_new, true, ni, &mi_new)) {
|
|
mft_new = 0;
|
|
/* Really this is not critical. */
|
|
} else if (mft_min > mft_new) {
|
|
mft_min = mft_new;
|
|
mi_min = mi_new;
|
|
} else {
|
|
ntfs_mark_rec_free(sbi, mft_new, true);
|
|
mft_new = 0;
|
|
ni_remove_mi(ni, mi_new);
|
|
}
|
|
|
|
attr = mi_find_attr(&ni->mi, NULL, ATTR_DATA, NULL, 0, NULL);
|
|
if (!attr) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
asize = le32_to_cpu(attr->size);
|
|
|
|
evcn = le64_to_cpu(attr->nres.evcn);
|
|
svcn = bytes_to_cluster(sbi, (u64)(mft_min + 1) << sbi->record_bits);
|
|
if (evcn + 1 >= svcn) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Split primary attribute [0 evcn] in two parts [0 svcn) + [svcn evcn].
|
|
*
|
|
* Update first part of ATTR_DATA in 'primary MFT.
|
|
*/
|
|
err = run_pack(run, 0, svcn, Add2Ptr(attr, SIZEOF_NONRESIDENT),
|
|
asize - SIZEOF_NONRESIDENT, &plen);
|
|
if (err < 0)
|
|
goto out;
|
|
|
|
run_size = ALIGN(err, 8);
|
|
err = 0;
|
|
|
|
if (plen < svcn) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
attr->nres.evcn = cpu_to_le64(svcn - 1);
|
|
attr->size = cpu_to_le32(run_size + SIZEOF_NONRESIDENT);
|
|
/* 'done' - How many bytes of primary MFT becomes free. */
|
|
done = asize - run_size - SIZEOF_NONRESIDENT;
|
|
le32_sub_cpu(&ni->mi.mrec->used, done);
|
|
|
|
/* Estimate packed size (run_buf=NULL). */
|
|
err = run_pack(run, svcn, evcn + 1 - svcn, NULL, sbi->record_size,
|
|
&plen);
|
|
if (err < 0)
|
|
goto out;
|
|
|
|
run_size = ALIGN(err, 8);
|
|
err = 0;
|
|
|
|
if (plen < evcn + 1 - svcn) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* This function may implicitly call expand attr_list.
|
|
* Insert second part of ATTR_DATA in 'mi_min'.
|
|
*/
|
|
attr = ni_ins_new_attr(ni, mi_min, NULL, ATTR_DATA, NULL, 0,
|
|
SIZEOF_NONRESIDENT + run_size,
|
|
SIZEOF_NONRESIDENT, svcn, NULL);
|
|
if (!attr) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if (IS_ERR(attr)) {
|
|
err = PTR_ERR(attr);
|
|
goto out;
|
|
}
|
|
|
|
attr->non_res = 1;
|
|
attr->name_off = SIZEOF_NONRESIDENT_LE;
|
|
attr->flags = 0;
|
|
|
|
/* This function can't fail - cause already checked above. */
|
|
run_pack(run, svcn, evcn + 1 - svcn, Add2Ptr(attr, SIZEOF_NONRESIDENT),
|
|
run_size, &plen);
|
|
|
|
attr->nres.svcn = cpu_to_le64(svcn);
|
|
attr->nres.evcn = cpu_to_le64(evcn);
|
|
attr->nres.run_off = cpu_to_le16(SIZEOF_NONRESIDENT);
|
|
|
|
out:
|
|
if (mft_new) {
|
|
ntfs_mark_rec_free(sbi, mft_new, true);
|
|
ni_remove_mi(ni, mi_new);
|
|
}
|
|
|
|
return !err && !done ? -EOPNOTSUPP : err;
|
|
}
|
|
|
|
/*
|
|
* ni_expand_list - Move all possible attributes out of primary record.
|
|
*/
|
|
int ni_expand_list(struct ntfs_inode *ni)
|
|
{
|
|
int err = 0;
|
|
u32 asize, done = 0;
|
|
struct ATTRIB *attr, *ins_attr;
|
|
struct ATTR_LIST_ENTRY *le;
|
|
bool is_mft = ni->mi.rno == MFT_REC_MFT;
|
|
struct MFT_REF ref;
|
|
|
|
mi_get_ref(&ni->mi, &ref);
|
|
le = NULL;
|
|
|
|
while ((le = al_enumerate(ni, le))) {
|
|
if (le->type == ATTR_STD)
|
|
continue;
|
|
|
|
if (memcmp(&ref, &le->ref, sizeof(struct MFT_REF)))
|
|
continue;
|
|
|
|
if (is_mft && le->type == ATTR_DATA)
|
|
continue;
|
|
|
|
/* Find attribute in primary record. */
|
|
attr = rec_find_attr_le(&ni->mi, le);
|
|
if (!attr) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
asize = le32_to_cpu(attr->size);
|
|
|
|
/* Always insert into new record to avoid collisions (deep recursive). */
|
|
err = ni_ins_attr_ext(ni, le, attr->type, attr_name(attr),
|
|
attr->name_len, asize, attr_svcn(attr),
|
|
le16_to_cpu(attr->name_off), true,
|
|
&ins_attr, NULL, NULL);
|
|
|
|
if (err)
|
|
goto out;
|
|
|
|
memcpy(ins_attr, attr, asize);
|
|
ins_attr->id = le->id;
|
|
/* Remove from primary record. */
|
|
mi_remove_attr(NULL, &ni->mi, attr);
|
|
|
|
done += asize;
|
|
goto out;
|
|
}
|
|
|
|
if (!is_mft) {
|
|
err = -EFBIG; /* Attr list is too big(?) */
|
|
goto out;
|
|
}
|
|
|
|
/* Split MFT data as much as possible. */
|
|
err = ni_expand_mft_list(ni);
|
|
|
|
out:
|
|
return !err && !done ? -EOPNOTSUPP : err;
|
|
}
|
|
|
|
/*
|
|
* ni_insert_nonresident - Insert new nonresident attribute.
|
|
*/
|
|
int ni_insert_nonresident(struct ntfs_inode *ni, enum ATTR_TYPE type,
|
|
const __le16 *name, u8 name_len,
|
|
const struct runs_tree *run, CLST svcn, CLST len,
|
|
__le16 flags, struct ATTRIB **new_attr,
|
|
struct mft_inode **mi, struct ATTR_LIST_ENTRY **le)
|
|
{
|
|
int err;
|
|
CLST plen;
|
|
struct ATTRIB *attr;
|
|
bool is_ext = (flags & (ATTR_FLAG_SPARSED | ATTR_FLAG_COMPRESSED)) &&
|
|
!svcn;
|
|
u32 name_size = ALIGN(name_len * sizeof(short), 8);
|
|
u32 name_off = is_ext ? SIZEOF_NONRESIDENT_EX : SIZEOF_NONRESIDENT;
|
|
u32 run_off = name_off + name_size;
|
|
u32 run_size, asize;
|
|
struct ntfs_sb_info *sbi = ni->mi.sbi;
|
|
|
|
/* Estimate packed size (run_buf=NULL). */
|
|
err = run_pack(run, svcn, len, NULL, sbi->max_bytes_per_attr - run_off,
|
|
&plen);
|
|
if (err < 0)
|
|
goto out;
|
|
|
|
run_size = ALIGN(err, 8);
|
|
|
|
if (plen < len) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
asize = run_off + run_size;
|
|
|
|
if (asize > sbi->max_bytes_per_attr) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
err = ni_insert_attr(ni, type, name, name_len, asize, name_off, svcn,
|
|
&attr, mi, le);
|
|
|
|
if (err)
|
|
goto out;
|
|
|
|
attr->non_res = 1;
|
|
attr->name_off = cpu_to_le16(name_off);
|
|
attr->flags = flags;
|
|
|
|
/* This function can't fail - cause already checked above. */
|
|
run_pack(run, svcn, len, Add2Ptr(attr, run_off), run_size, &plen);
|
|
|
|
attr->nres.svcn = cpu_to_le64(svcn);
|
|
attr->nres.evcn = cpu_to_le64((u64)svcn + len - 1);
|
|
|
|
if (new_attr)
|
|
*new_attr = attr;
|
|
|
|
*(__le64 *)&attr->nres.run_off = cpu_to_le64(run_off);
|
|
|
|
attr->nres.alloc_size =
|
|
svcn ? 0 : cpu_to_le64((u64)len << ni->mi.sbi->cluster_bits);
|
|
attr->nres.data_size = attr->nres.alloc_size;
|
|
attr->nres.valid_size = attr->nres.alloc_size;
|
|
|
|
if (is_ext) {
|
|
if (flags & ATTR_FLAG_COMPRESSED)
|
|
attr->nres.c_unit = COMPRESSION_UNIT;
|
|
attr->nres.total_size = attr->nres.alloc_size;
|
|
}
|
|
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* ni_insert_resident - Inserts new resident attribute.
|
|
*/
|
|
int ni_insert_resident(struct ntfs_inode *ni, u32 data_size,
|
|
enum ATTR_TYPE type, const __le16 *name, u8 name_len,
|
|
struct ATTRIB **new_attr, struct mft_inode **mi,
|
|
struct ATTR_LIST_ENTRY **le)
|
|
{
|
|
int err;
|
|
u32 name_size = ALIGN(name_len * sizeof(short), 8);
|
|
u32 asize = SIZEOF_RESIDENT + name_size + ALIGN(data_size, 8);
|
|
struct ATTRIB *attr;
|
|
|
|
err = ni_insert_attr(ni, type, name, name_len, asize, SIZEOF_RESIDENT,
|
|
0, &attr, mi, le);
|
|
if (err)
|
|
return err;
|
|
|
|
attr->non_res = 0;
|
|
attr->flags = 0;
|
|
|
|
attr->res.data_size = cpu_to_le32(data_size);
|
|
attr->res.data_off = cpu_to_le16(SIZEOF_RESIDENT + name_size);
|
|
if (type == ATTR_NAME) {
|
|
attr->res.flags = RESIDENT_FLAG_INDEXED;
|
|
|
|
/* is_attr_indexed(attr)) == true */
|
|
le16_add_cpu(&ni->mi.mrec->hard_links, 1);
|
|
ni->mi.dirty = true;
|
|
}
|
|
attr->res.res = 0;
|
|
|
|
if (new_attr)
|
|
*new_attr = attr;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* ni_remove_attr_le - Remove attribute from record.
|
|
*/
|
|
void ni_remove_attr_le(struct ntfs_inode *ni, struct ATTRIB *attr,
|
|
struct mft_inode *mi, struct ATTR_LIST_ENTRY *le)
|
|
{
|
|
mi_remove_attr(ni, mi, attr);
|
|
|
|
if (le)
|
|
al_remove_le(ni, le);
|
|
}
|
|
|
|
/*
|
|
* ni_delete_all - Remove all attributes and frees allocates space.
|
|
*
|
|
* ntfs_evict_inode->ntfs_clear_inode->ni_delete_all (if no links).
|
|
*/
|
|
int ni_delete_all(struct ntfs_inode *ni)
|
|
{
|
|
int err;
|
|
struct ATTR_LIST_ENTRY *le = NULL;
|
|
struct ATTRIB *attr = NULL;
|
|
struct rb_node *node;
|
|
u16 roff;
|
|
u32 asize;
|
|
CLST svcn, evcn;
|
|
struct ntfs_sb_info *sbi = ni->mi.sbi;
|
|
bool nt3 = is_ntfs3(sbi);
|
|
struct MFT_REF ref;
|
|
|
|
while ((attr = ni_enum_attr_ex(ni, attr, &le, NULL))) {
|
|
if (!nt3 || attr->name_len) {
|
|
;
|
|
} else if (attr->type == ATTR_REPARSE) {
|
|
mi_get_ref(&ni->mi, &ref);
|
|
ntfs_remove_reparse(sbi, 0, &ref);
|
|
} else if (attr->type == ATTR_ID && !attr->non_res &&
|
|
le32_to_cpu(attr->res.data_size) >=
|
|
sizeof(struct GUID)) {
|
|
ntfs_objid_remove(sbi, resident_data(attr));
|
|
}
|
|
|
|
if (!attr->non_res)
|
|
continue;
|
|
|
|
svcn = le64_to_cpu(attr->nres.svcn);
|
|
evcn = le64_to_cpu(attr->nres.evcn);
|
|
|
|
if (evcn + 1 <= svcn)
|
|
continue;
|
|
|
|
asize = le32_to_cpu(attr->size);
|
|
roff = le16_to_cpu(attr->nres.run_off);
|
|
|
|
if (roff > asize)
|
|
return -EINVAL;
|
|
|
|
/* run==1 means unpack and deallocate. */
|
|
run_unpack_ex(RUN_DEALLOCATE, sbi, ni->mi.rno, svcn, evcn, svcn,
|
|
Add2Ptr(attr, roff), asize - roff);
|
|
}
|
|
|
|
if (ni->attr_list.size) {
|
|
run_deallocate(ni->mi.sbi, &ni->attr_list.run, true);
|
|
al_destroy(ni);
|
|
}
|
|
|
|
/* Free all subrecords. */
|
|
for (node = rb_first(&ni->mi_tree); node;) {
|
|
struct rb_node *next = rb_next(node);
|
|
struct mft_inode *mi = rb_entry(node, struct mft_inode, node);
|
|
|
|
clear_rec_inuse(mi->mrec);
|
|
mi->dirty = true;
|
|
mi_write(mi, 0);
|
|
|
|
ntfs_mark_rec_free(sbi, mi->rno, false);
|
|
ni_remove_mi(ni, mi);
|
|
mi_put(mi);
|
|
node = next;
|
|
}
|
|
|
|
/* Free base record. */
|
|
clear_rec_inuse(ni->mi.mrec);
|
|
ni->mi.dirty = true;
|
|
err = mi_write(&ni->mi, 0);
|
|
|
|
ntfs_mark_rec_free(sbi, ni->mi.rno, false);
|
|
|
|
return err;
|
|
}
|
|
|
|
/* ni_fname_name
|
|
*
|
|
* Return: File name attribute by its value.
|
|
*/
|
|
struct ATTR_FILE_NAME *ni_fname_name(struct ntfs_inode *ni,
|
|
const struct le_str *uni,
|
|
const struct MFT_REF *home_dir,
|
|
struct mft_inode **mi,
|
|
struct ATTR_LIST_ENTRY **le)
|
|
{
|
|
struct ATTRIB *attr = NULL;
|
|
struct ATTR_FILE_NAME *fname;
|
|
|
|
if (le)
|
|
*le = NULL;
|
|
|
|
/* Enumerate all names. */
|
|
next:
|
|
attr = ni_find_attr(ni, attr, le, ATTR_NAME, NULL, 0, NULL, mi);
|
|
if (!attr)
|
|
return NULL;
|
|
|
|
fname = resident_data_ex(attr, SIZEOF_ATTRIBUTE_FILENAME);
|
|
if (!fname)
|
|
goto next;
|
|
|
|
if (home_dir && memcmp(home_dir, &fname->home, sizeof(*home_dir)))
|
|
goto next;
|
|
|
|
if (!uni)
|
|
return fname;
|
|
|
|
if (uni->len != fname->name_len)
|
|
goto next;
|
|
|
|
if (ntfs_cmp_names(uni->name, uni->len, fname->name, uni->len, NULL,
|
|
false))
|
|
goto next;
|
|
return fname;
|
|
}
|
|
|
|
/*
|
|
* ni_fname_type
|
|
*
|
|
* Return: File name attribute with given type.
|
|
*/
|
|
struct ATTR_FILE_NAME *ni_fname_type(struct ntfs_inode *ni, u8 name_type,
|
|
struct mft_inode **mi,
|
|
struct ATTR_LIST_ENTRY **le)
|
|
{
|
|
struct ATTRIB *attr = NULL;
|
|
struct ATTR_FILE_NAME *fname;
|
|
|
|
*le = NULL;
|
|
|
|
if (name_type == FILE_NAME_POSIX)
|
|
return NULL;
|
|
|
|
/* Enumerate all names. */
|
|
for (;;) {
|
|
attr = ni_find_attr(ni, attr, le, ATTR_NAME, NULL, 0, NULL, mi);
|
|
if (!attr)
|
|
return NULL;
|
|
|
|
fname = resident_data_ex(attr, SIZEOF_ATTRIBUTE_FILENAME);
|
|
if (fname && name_type == fname->type)
|
|
return fname;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ni_new_attr_flags
|
|
*
|
|
* Process compressed/sparsed in special way.
|
|
* NOTE: You need to set ni->std_fa = new_fa
|
|
* after this function to keep internal structures in consistency.
|
|
*/
|
|
int ni_new_attr_flags(struct ntfs_inode *ni, enum FILE_ATTRIBUTE new_fa)
|
|
{
|
|
struct ATTRIB *attr;
|
|
struct mft_inode *mi;
|
|
__le16 new_aflags;
|
|
u32 new_asize;
|
|
|
|
attr = ni_find_attr(ni, NULL, NULL, ATTR_DATA, NULL, 0, NULL, &mi);
|
|
if (!attr)
|
|
return -EINVAL;
|
|
|
|
new_aflags = attr->flags;
|
|
|
|
if (new_fa & FILE_ATTRIBUTE_SPARSE_FILE)
|
|
new_aflags |= ATTR_FLAG_SPARSED;
|
|
else
|
|
new_aflags &= ~ATTR_FLAG_SPARSED;
|
|
|
|
if (new_fa & FILE_ATTRIBUTE_COMPRESSED)
|
|
new_aflags |= ATTR_FLAG_COMPRESSED;
|
|
else
|
|
new_aflags &= ~ATTR_FLAG_COMPRESSED;
|
|
|
|
if (new_aflags == attr->flags)
|
|
return 0;
|
|
|
|
if ((new_aflags & (ATTR_FLAG_COMPRESSED | ATTR_FLAG_SPARSED)) ==
|
|
(ATTR_FLAG_COMPRESSED | ATTR_FLAG_SPARSED)) {
|
|
ntfs_inode_warn(&ni->vfs_inode,
|
|
"file can't be sparsed and compressed");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (!attr->non_res)
|
|
goto out;
|
|
|
|
if (attr->nres.data_size) {
|
|
ntfs_inode_warn(
|
|
&ni->vfs_inode,
|
|
"one can change sparsed/compressed only for empty files");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
/* Resize nonresident empty attribute in-place only. */
|
|
new_asize = (new_aflags & (ATTR_FLAG_COMPRESSED | ATTR_FLAG_SPARSED)) ?
|
|
(SIZEOF_NONRESIDENT_EX + 8) :
|
|
(SIZEOF_NONRESIDENT + 8);
|
|
|
|
if (!mi_resize_attr(mi, attr, new_asize - le32_to_cpu(attr->size)))
|
|
return -EOPNOTSUPP;
|
|
|
|
if (new_aflags & ATTR_FLAG_SPARSED) {
|
|
attr->name_off = SIZEOF_NONRESIDENT_EX_LE;
|
|
/* Windows uses 16 clusters per frame but supports one cluster per frame too. */
|
|
attr->nres.c_unit = 0;
|
|
ni->vfs_inode.i_mapping->a_ops = &ntfs_aops;
|
|
} else if (new_aflags & ATTR_FLAG_COMPRESSED) {
|
|
attr->name_off = SIZEOF_NONRESIDENT_EX_LE;
|
|
/* The only allowed: 16 clusters per frame. */
|
|
attr->nres.c_unit = NTFS_LZNT_CUNIT;
|
|
ni->vfs_inode.i_mapping->a_ops = &ntfs_aops_cmpr;
|
|
} else {
|
|
attr->name_off = SIZEOF_NONRESIDENT_LE;
|
|
/* Normal files. */
|
|
attr->nres.c_unit = 0;
|
|
ni->vfs_inode.i_mapping->a_ops = &ntfs_aops;
|
|
}
|
|
attr->nres.run_off = attr->name_off;
|
|
out:
|
|
attr->flags = new_aflags;
|
|
mi->dirty = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* ni_parse_reparse
|
|
*
|
|
* buffer - memory for reparse buffer header
|
|
*/
|
|
enum REPARSE_SIGN ni_parse_reparse(struct ntfs_inode *ni, struct ATTRIB *attr,
|
|
struct REPARSE_DATA_BUFFER *buffer)
|
|
{
|
|
const struct REPARSE_DATA_BUFFER *rp = NULL;
|
|
u8 bits;
|
|
u16 len;
|
|
typeof(rp->CompressReparseBuffer) *cmpr;
|
|
|
|
/* Try to estimate reparse point. */
|
|
if (!attr->non_res) {
|
|
rp = resident_data_ex(attr, sizeof(struct REPARSE_DATA_BUFFER));
|
|
} else if (le64_to_cpu(attr->nres.data_size) >=
|
|
sizeof(struct REPARSE_DATA_BUFFER)) {
|
|
struct runs_tree run;
|
|
|
|
run_init(&run);
|
|
|
|
if (!attr_load_runs_vcn(ni, ATTR_REPARSE, NULL, 0, &run, 0) &&
|
|
!ntfs_read_run_nb(ni->mi.sbi, &run, 0, buffer,
|
|
sizeof(struct REPARSE_DATA_BUFFER),
|
|
NULL)) {
|
|
rp = buffer;
|
|
}
|
|
|
|
run_close(&run);
|
|
}
|
|
|
|
if (!rp)
|
|
return REPARSE_NONE;
|
|
|
|
len = le16_to_cpu(rp->ReparseDataLength);
|
|
switch (rp->ReparseTag) {
|
|
case (IO_REPARSE_TAG_MICROSOFT | IO_REPARSE_TAG_SYMBOLIC_LINK):
|
|
break; /* Symbolic link. */
|
|
case IO_REPARSE_TAG_MOUNT_POINT:
|
|
break; /* Mount points and junctions. */
|
|
case IO_REPARSE_TAG_SYMLINK:
|
|
break;
|
|
case IO_REPARSE_TAG_COMPRESS:
|
|
/*
|
|
* WOF - Windows Overlay Filter - Used to compress files with
|
|
* LZX/Xpress.
|
|
*
|
|
* Unlike native NTFS file compression, the Windows
|
|
* Overlay Filter supports only read operations. This means
|
|
* that it doesn't need to sector-align each compressed chunk,
|
|
* so the compressed data can be packed more tightly together.
|
|
* If you open the file for writing, the WOF just decompresses
|
|
* the entire file, turning it back into a plain file.
|
|
*
|
|
* Ntfs3 driver decompresses the entire file only on write or
|
|
* change size requests.
|
|
*/
|
|
|
|
cmpr = &rp->CompressReparseBuffer;
|
|
if (len < sizeof(*cmpr) ||
|
|
cmpr->WofVersion != WOF_CURRENT_VERSION ||
|
|
cmpr->WofProvider != WOF_PROVIDER_SYSTEM ||
|
|
cmpr->ProviderVer != WOF_PROVIDER_CURRENT_VERSION) {
|
|
return REPARSE_NONE;
|
|
}
|
|
|
|
switch (cmpr->CompressionFormat) {
|
|
case WOF_COMPRESSION_XPRESS4K:
|
|
bits = 0xc; // 4k
|
|
break;
|
|
case WOF_COMPRESSION_XPRESS8K:
|
|
bits = 0xd; // 8k
|
|
break;
|
|
case WOF_COMPRESSION_XPRESS16K:
|
|
bits = 0xe; // 16k
|
|
break;
|
|
case WOF_COMPRESSION_LZX32K:
|
|
bits = 0xf; // 32k
|
|
break;
|
|
default:
|
|
bits = 0x10; // 64k
|
|
break;
|
|
}
|
|
ni_set_ext_compress_bits(ni, bits);
|
|
return REPARSE_COMPRESSED;
|
|
|
|
case IO_REPARSE_TAG_DEDUP:
|
|
ni->ni_flags |= NI_FLAG_DEDUPLICATED;
|
|
return REPARSE_DEDUPLICATED;
|
|
|
|
default:
|
|
if (rp->ReparseTag & IO_REPARSE_TAG_NAME_SURROGATE)
|
|
break;
|
|
|
|
return REPARSE_NONE;
|
|
}
|
|
|
|
if (buffer != rp)
|
|
memcpy(buffer, rp, sizeof(struct REPARSE_DATA_BUFFER));
|
|
|
|
/* Looks like normal symlink. */
|
|
return REPARSE_LINK;
|
|
}
|
|
|
|
/*
|
|
* ni_fiemap - Helper for file_fiemap().
|
|
*
|
|
* Assumed ni_lock.
|
|
* TODO: Less aggressive locks.
|
|
*/
|
|
int ni_fiemap(struct ntfs_inode *ni, struct fiemap_extent_info *fieinfo,
|
|
__u64 vbo, __u64 len)
|
|
{
|
|
int err = 0;
|
|
struct ntfs_sb_info *sbi = ni->mi.sbi;
|
|
u8 cluster_bits = sbi->cluster_bits;
|
|
struct runs_tree *run;
|
|
struct rw_semaphore *run_lock;
|
|
struct ATTRIB *attr;
|
|
CLST vcn = vbo >> cluster_bits;
|
|
CLST lcn, clen;
|
|
u64 valid = ni->i_valid;
|
|
u64 lbo, bytes;
|
|
u64 end, alloc_size;
|
|
size_t idx = -1;
|
|
u32 flags;
|
|
bool ok;
|
|
|
|
if (S_ISDIR(ni->vfs_inode.i_mode)) {
|
|
run = &ni->dir.alloc_run;
|
|
attr = ni_find_attr(ni, NULL, NULL, ATTR_ALLOC, I30_NAME,
|
|
ARRAY_SIZE(I30_NAME), NULL, NULL);
|
|
run_lock = &ni->dir.run_lock;
|
|
} else {
|
|
run = &ni->file.run;
|
|
attr = ni_find_attr(ni, NULL, NULL, ATTR_DATA, NULL, 0, NULL,
|
|
NULL);
|
|
if (!attr) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
if (is_attr_compressed(attr)) {
|
|
/* Unfortunately cp -r incorrectly treats compressed clusters. */
|
|
err = -EOPNOTSUPP;
|
|
ntfs_inode_warn(
|
|
&ni->vfs_inode,
|
|
"fiemap is not supported for compressed file (cp -r)");
|
|
goto out;
|
|
}
|
|
run_lock = &ni->file.run_lock;
|
|
}
|
|
|
|
if (!attr || !attr->non_res) {
|
|
err = fiemap_fill_next_extent(
|
|
fieinfo, 0, 0,
|
|
attr ? le32_to_cpu(attr->res.data_size) : 0,
|
|
FIEMAP_EXTENT_DATA_INLINE | FIEMAP_EXTENT_LAST |
|
|
FIEMAP_EXTENT_MERGED);
|
|
goto out;
|
|
}
|
|
|
|
end = vbo + len;
|
|
alloc_size = le64_to_cpu(attr->nres.alloc_size);
|
|
if (end > alloc_size)
|
|
end = alloc_size;
|
|
|
|
down_read(run_lock);
|
|
|
|
while (vbo < end) {
|
|
if (idx == -1) {
|
|
ok = run_lookup_entry(run, vcn, &lcn, &clen, &idx);
|
|
} else {
|
|
CLST vcn_next = vcn;
|
|
|
|
ok = run_get_entry(run, ++idx, &vcn, &lcn, &clen) &&
|
|
vcn == vcn_next;
|
|
if (!ok)
|
|
vcn = vcn_next;
|
|
}
|
|
|
|
if (!ok) {
|
|
up_read(run_lock);
|
|
down_write(run_lock);
|
|
|
|
err = attr_load_runs_vcn(ni, attr->type,
|
|
attr_name(attr),
|
|
attr->name_len, run, vcn);
|
|
|
|
up_write(run_lock);
|
|
down_read(run_lock);
|
|
|
|
if (err)
|
|
break;
|
|
|
|
ok = run_lookup_entry(run, vcn, &lcn, &clen, &idx);
|
|
|
|
if (!ok) {
|
|
err = -EINVAL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!clen) {
|
|
err = -EINVAL; // ?
|
|
break;
|
|
}
|
|
|
|
if (lcn == SPARSE_LCN) {
|
|
vcn += clen;
|
|
vbo = (u64)vcn << cluster_bits;
|
|
continue;
|
|
}
|
|
|
|
flags = FIEMAP_EXTENT_MERGED;
|
|
if (S_ISDIR(ni->vfs_inode.i_mode)) {
|
|
;
|
|
} else if (is_attr_compressed(attr)) {
|
|
CLST clst_data;
|
|
|
|
err = attr_is_frame_compressed(
|
|
ni, attr, vcn >> attr->nres.c_unit, &clst_data);
|
|
if (err)
|
|
break;
|
|
if (clst_data < NTFS_LZNT_CLUSTERS)
|
|
flags |= FIEMAP_EXTENT_ENCODED;
|
|
} else if (is_attr_encrypted(attr)) {
|
|
flags |= FIEMAP_EXTENT_DATA_ENCRYPTED;
|
|
}
|
|
|
|
vbo = (u64)vcn << cluster_bits;
|
|
bytes = (u64)clen << cluster_bits;
|
|
lbo = (u64)lcn << cluster_bits;
|
|
|
|
vcn += clen;
|
|
|
|
if (vbo + bytes >= end)
|
|
bytes = end - vbo;
|
|
|
|
if (vbo + bytes <= valid) {
|
|
;
|
|
} else if (vbo >= valid) {
|
|
flags |= FIEMAP_EXTENT_UNWRITTEN;
|
|
} else {
|
|
/* vbo < valid && valid < vbo + bytes */
|
|
u64 dlen = valid - vbo;
|
|
|
|
if (vbo + dlen >= end)
|
|
flags |= FIEMAP_EXTENT_LAST;
|
|
|
|
err = fiemap_fill_next_extent(fieinfo, vbo, lbo, dlen,
|
|
flags);
|
|
if (err < 0)
|
|
break;
|
|
if (err == 1) {
|
|
err = 0;
|
|
break;
|
|
}
|
|
|
|
vbo = valid;
|
|
bytes -= dlen;
|
|
if (!bytes)
|
|
continue;
|
|
|
|
lbo += dlen;
|
|
flags |= FIEMAP_EXTENT_UNWRITTEN;
|
|
}
|
|
|
|
if (vbo + bytes >= end)
|
|
flags |= FIEMAP_EXTENT_LAST;
|
|
|
|
err = fiemap_fill_next_extent(fieinfo, vbo, lbo, bytes, flags);
|
|
if (err < 0)
|
|
break;
|
|
if (err == 1) {
|
|
err = 0;
|
|
break;
|
|
}
|
|
|
|
vbo += bytes;
|
|
}
|
|
|
|
up_read(run_lock);
|
|
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* ni_readpage_cmpr
|
|
*
|
|
* When decompressing, we typically obtain more than one page per reference.
|
|
* We inject the additional pages into the page cache.
|
|
*/
|
|
int ni_readpage_cmpr(struct ntfs_inode *ni, struct page *page)
|
|
{
|
|
int err;
|
|
struct ntfs_sb_info *sbi = ni->mi.sbi;
|
|
struct address_space *mapping = page->mapping;
|
|
pgoff_t index = page->index;
|
|
u64 frame_vbo, vbo = (u64)index << PAGE_SHIFT;
|
|
struct page **pages = NULL; /* Array of at most 16 pages. stack? */
|
|
u8 frame_bits;
|
|
CLST frame;
|
|
u32 i, idx, frame_size, pages_per_frame;
|
|
gfp_t gfp_mask;
|
|
struct page *pg;
|
|
|
|
if (vbo >= ni->vfs_inode.i_size) {
|
|
SetPageUptodate(page);
|
|
err = 0;
|
|
goto out;
|
|
}
|
|
|
|
if (ni->ni_flags & NI_FLAG_COMPRESSED_MASK) {
|
|
/* Xpress or LZX. */
|
|
frame_bits = ni_ext_compress_bits(ni);
|
|
} else {
|
|
/* LZNT compression. */
|
|
frame_bits = NTFS_LZNT_CUNIT + sbi->cluster_bits;
|
|
}
|
|
frame_size = 1u << frame_bits;
|
|
frame = vbo >> frame_bits;
|
|
frame_vbo = (u64)frame << frame_bits;
|
|
idx = (vbo - frame_vbo) >> PAGE_SHIFT;
|
|
|
|
pages_per_frame = frame_size >> PAGE_SHIFT;
|
|
pages = kcalloc(pages_per_frame, sizeof(struct page *), GFP_NOFS);
|
|
if (!pages) {
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
pages[idx] = page;
|
|
index = frame_vbo >> PAGE_SHIFT;
|
|
gfp_mask = mapping_gfp_mask(mapping);
|
|
|
|
for (i = 0; i < pages_per_frame; i++, index++) {
|
|
if (i == idx)
|
|
continue;
|
|
|
|
pg = find_or_create_page(mapping, index, gfp_mask);
|
|
if (!pg) {
|
|
err = -ENOMEM;
|
|
goto out1;
|
|
}
|
|
pages[i] = pg;
|
|
}
|
|
|
|
err = ni_read_frame(ni, frame_vbo, pages, pages_per_frame);
|
|
|
|
out1:
|
|
if (err)
|
|
SetPageError(page);
|
|
|
|
for (i = 0; i < pages_per_frame; i++) {
|
|
pg = pages[i];
|
|
if (i == idx || !pg)
|
|
continue;
|
|
unlock_page(pg);
|
|
put_page(pg);
|
|
}
|
|
|
|
out:
|
|
/* At this point, err contains 0 or -EIO depending on the "critical" page. */
|
|
kfree(pages);
|
|
unlock_page(page);
|
|
|
|
return err;
|
|
}
|
|
|
|
#ifdef CONFIG_NTFS3_LZX_XPRESS
|
|
/*
|
|
* ni_decompress_file - Decompress LZX/Xpress compressed file.
|
|
*
|
|
* Remove ATTR_DATA::WofCompressedData.
|
|
* Remove ATTR_REPARSE.
|
|
*/
|
|
int ni_decompress_file(struct ntfs_inode *ni)
|
|
{
|
|
struct ntfs_sb_info *sbi = ni->mi.sbi;
|
|
struct inode *inode = &ni->vfs_inode;
|
|
loff_t i_size = inode->i_size;
|
|
struct address_space *mapping = inode->i_mapping;
|
|
gfp_t gfp_mask = mapping_gfp_mask(mapping);
|
|
struct page **pages = NULL;
|
|
struct ATTR_LIST_ENTRY *le;
|
|
struct ATTRIB *attr;
|
|
CLST vcn, cend, lcn, clen, end;
|
|
pgoff_t index;
|
|
u64 vbo;
|
|
u8 frame_bits;
|
|
u32 i, frame_size, pages_per_frame, bytes;
|
|
struct mft_inode *mi;
|
|
int err;
|
|
|
|
/* Clusters for decompressed data. */
|
|
cend = bytes_to_cluster(sbi, i_size);
|
|
|
|
if (!i_size)
|
|
goto remove_wof;
|
|
|
|
/* Check in advance. */
|
|
if (cend > wnd_zeroes(&sbi->used.bitmap)) {
|
|
err = -ENOSPC;
|
|
goto out;
|
|
}
|
|
|
|
frame_bits = ni_ext_compress_bits(ni);
|
|
frame_size = 1u << frame_bits;
|
|
pages_per_frame = frame_size >> PAGE_SHIFT;
|
|
pages = kcalloc(pages_per_frame, sizeof(struct page *), GFP_NOFS);
|
|
if (!pages) {
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Step 1: Decompress data and copy to new allocated clusters.
|
|
*/
|
|
index = 0;
|
|
for (vbo = 0; vbo < i_size; vbo += bytes) {
|
|
u32 nr_pages;
|
|
bool new;
|
|
|
|
if (vbo + frame_size > i_size) {
|
|
bytes = i_size - vbo;
|
|
nr_pages = (bytes + PAGE_SIZE - 1) >> PAGE_SHIFT;
|
|
} else {
|
|
nr_pages = pages_per_frame;
|
|
bytes = frame_size;
|
|
}
|
|
|
|
end = bytes_to_cluster(sbi, vbo + bytes);
|
|
|
|
for (vcn = vbo >> sbi->cluster_bits; vcn < end; vcn += clen) {
|
|
err = attr_data_get_block(ni, vcn, cend - vcn, &lcn,
|
|
&clen, &new, false);
|
|
if (err)
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < pages_per_frame; i++, index++) {
|
|
struct page *pg;
|
|
|
|
pg = find_or_create_page(mapping, index, gfp_mask);
|
|
if (!pg) {
|
|
while (i--) {
|
|
unlock_page(pages[i]);
|
|
put_page(pages[i]);
|
|
}
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
pages[i] = pg;
|
|
}
|
|
|
|
err = ni_read_frame(ni, vbo, pages, pages_per_frame);
|
|
|
|
if (!err) {
|
|
down_read(&ni->file.run_lock);
|
|
err = ntfs_bio_pages(sbi, &ni->file.run, pages,
|
|
nr_pages, vbo, bytes,
|
|
REQ_OP_WRITE);
|
|
up_read(&ni->file.run_lock);
|
|
}
|
|
|
|
for (i = 0; i < pages_per_frame; i++) {
|
|
unlock_page(pages[i]);
|
|
put_page(pages[i]);
|
|
}
|
|
|
|
if (err)
|
|
goto out;
|
|
|
|
cond_resched();
|
|
}
|
|
|
|
remove_wof:
|
|
/*
|
|
* Step 2: Deallocate attributes ATTR_DATA::WofCompressedData
|
|
* and ATTR_REPARSE.
|
|
*/
|
|
attr = NULL;
|
|
le = NULL;
|
|
while ((attr = ni_enum_attr_ex(ni, attr, &le, NULL))) {
|
|
CLST svcn, evcn;
|
|
u32 asize, roff;
|
|
|
|
if (attr->type == ATTR_REPARSE) {
|
|
struct MFT_REF ref;
|
|
|
|
mi_get_ref(&ni->mi, &ref);
|
|
ntfs_remove_reparse(sbi, 0, &ref);
|
|
}
|
|
|
|
if (!attr->non_res)
|
|
continue;
|
|
|
|
if (attr->type != ATTR_REPARSE &&
|
|
(attr->type != ATTR_DATA ||
|
|
attr->name_len != ARRAY_SIZE(WOF_NAME) ||
|
|
memcmp(attr_name(attr), WOF_NAME, sizeof(WOF_NAME))))
|
|
continue;
|
|
|
|
svcn = le64_to_cpu(attr->nres.svcn);
|
|
evcn = le64_to_cpu(attr->nres.evcn);
|
|
|
|
if (evcn + 1 <= svcn)
|
|
continue;
|
|
|
|
asize = le32_to_cpu(attr->size);
|
|
roff = le16_to_cpu(attr->nres.run_off);
|
|
|
|
if (roff > asize) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
/*run==1 Means unpack and deallocate. */
|
|
run_unpack_ex(RUN_DEALLOCATE, sbi, ni->mi.rno, svcn, evcn, svcn,
|
|
Add2Ptr(attr, roff), asize - roff);
|
|
}
|
|
|
|
/*
|
|
* Step 3: Remove attribute ATTR_DATA::WofCompressedData.
|
|
*/
|
|
err = ni_remove_attr(ni, ATTR_DATA, WOF_NAME, ARRAY_SIZE(WOF_NAME),
|
|
false, NULL);
|
|
if (err)
|
|
goto out;
|
|
|
|
/*
|
|
* Step 4: Remove ATTR_REPARSE.
|
|
*/
|
|
err = ni_remove_attr(ni, ATTR_REPARSE, NULL, 0, false, NULL);
|
|
if (err)
|
|
goto out;
|
|
|
|
/*
|
|
* Step 5: Remove sparse flag from data attribute.
|
|
*/
|
|
attr = ni_find_attr(ni, NULL, NULL, ATTR_DATA, NULL, 0, NULL, &mi);
|
|
if (!attr) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if (attr->non_res && is_attr_sparsed(attr)) {
|
|
/* Sparsed attribute header is 8 bytes bigger than normal. */
|
|
struct MFT_REC *rec = mi->mrec;
|
|
u32 used = le32_to_cpu(rec->used);
|
|
u32 asize = le32_to_cpu(attr->size);
|
|
u16 roff = le16_to_cpu(attr->nres.run_off);
|
|
char *rbuf = Add2Ptr(attr, roff);
|
|
|
|
memmove(rbuf - 8, rbuf, used - PtrOffset(rec, rbuf));
|
|
attr->size = cpu_to_le32(asize - 8);
|
|
attr->flags &= ~ATTR_FLAG_SPARSED;
|
|
attr->nres.run_off = cpu_to_le16(roff - 8);
|
|
attr->nres.c_unit = 0;
|
|
rec->used = cpu_to_le32(used - 8);
|
|
mi->dirty = true;
|
|
ni->std_fa &= ~(FILE_ATTRIBUTE_SPARSE_FILE |
|
|
FILE_ATTRIBUTE_REPARSE_POINT);
|
|
|
|
mark_inode_dirty(inode);
|
|
}
|
|
|
|
/* Clear cached flag. */
|
|
ni->ni_flags &= ~NI_FLAG_COMPRESSED_MASK;
|
|
if (ni->file.offs_page) {
|
|
put_page(ni->file.offs_page);
|
|
ni->file.offs_page = NULL;
|
|
}
|
|
mapping->a_ops = &ntfs_aops;
|
|
|
|
out:
|
|
kfree(pages);
|
|
if (err)
|
|
_ntfs_bad_inode(inode);
|
|
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* decompress_lzx_xpress - External compression LZX/Xpress.
|
|
*/
|
|
static int decompress_lzx_xpress(struct ntfs_sb_info *sbi, const char *cmpr,
|
|
size_t cmpr_size, void *unc, size_t unc_size,
|
|
u32 frame_size)
|
|
{
|
|
int err;
|
|
void *ctx;
|
|
|
|
if (cmpr_size == unc_size) {
|
|
/* Frame not compressed. */
|
|
memcpy(unc, cmpr, unc_size);
|
|
return 0;
|
|
}
|
|
|
|
err = 0;
|
|
if (frame_size == 0x8000) {
|
|
mutex_lock(&sbi->compress.mtx_lzx);
|
|
/* LZX: Frame compressed. */
|
|
ctx = sbi->compress.lzx;
|
|
if (!ctx) {
|
|
/* Lazy initialize LZX decompress context. */
|
|
ctx = lzx_allocate_decompressor();
|
|
if (!ctx) {
|
|
err = -ENOMEM;
|
|
goto out1;
|
|
}
|
|
|
|
sbi->compress.lzx = ctx;
|
|
}
|
|
|
|
if (lzx_decompress(ctx, cmpr, cmpr_size, unc, unc_size)) {
|
|
/* Treat all errors as "invalid argument". */
|
|
err = -EINVAL;
|
|
}
|
|
out1:
|
|
mutex_unlock(&sbi->compress.mtx_lzx);
|
|
} else {
|
|
/* XPRESS: Frame compressed. */
|
|
mutex_lock(&sbi->compress.mtx_xpress);
|
|
ctx = sbi->compress.xpress;
|
|
if (!ctx) {
|
|
/* Lazy initialize Xpress decompress context. */
|
|
ctx = xpress_allocate_decompressor();
|
|
if (!ctx) {
|
|
err = -ENOMEM;
|
|
goto out2;
|
|
}
|
|
|
|
sbi->compress.xpress = ctx;
|
|
}
|
|
|
|
if (xpress_decompress(ctx, cmpr, cmpr_size, unc, unc_size)) {
|
|
/* Treat all errors as "invalid argument". */
|
|
err = -EINVAL;
|
|
}
|
|
out2:
|
|
mutex_unlock(&sbi->compress.mtx_xpress);
|
|
}
|
|
return err;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* ni_read_frame
|
|
*
|
|
* Pages - Array of locked pages.
|
|
*/
|
|
int ni_read_frame(struct ntfs_inode *ni, u64 frame_vbo, struct page **pages,
|
|
u32 pages_per_frame)
|
|
{
|
|
int err;
|
|
struct ntfs_sb_info *sbi = ni->mi.sbi;
|
|
u8 cluster_bits = sbi->cluster_bits;
|
|
char *frame_ondisk = NULL;
|
|
char *frame_mem = NULL;
|
|
struct page **pages_disk = NULL;
|
|
struct ATTR_LIST_ENTRY *le = NULL;
|
|
struct runs_tree *run = &ni->file.run;
|
|
u64 valid_size = ni->i_valid;
|
|
u64 vbo_disk;
|
|
size_t unc_size;
|
|
u32 frame_size, i, npages_disk, ondisk_size;
|
|
struct page *pg;
|
|
struct ATTRIB *attr;
|
|
CLST frame, clst_data;
|
|
|
|
/*
|
|
* To simplify decompress algorithm do vmap for source
|
|
* and target pages.
|
|
*/
|
|
for (i = 0; i < pages_per_frame; i++)
|
|
kmap(pages[i]);
|
|
|
|
frame_size = pages_per_frame << PAGE_SHIFT;
|
|
frame_mem = vmap(pages, pages_per_frame, VM_MAP, PAGE_KERNEL);
|
|
if (!frame_mem) {
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
attr = ni_find_attr(ni, NULL, &le, ATTR_DATA, NULL, 0, NULL, NULL);
|
|
if (!attr) {
|
|
err = -ENOENT;
|
|
goto out1;
|
|
}
|
|
|
|
if (!attr->non_res) {
|
|
u32 data_size = le32_to_cpu(attr->res.data_size);
|
|
|
|
memset(frame_mem, 0, frame_size);
|
|
if (frame_vbo < data_size) {
|
|
ondisk_size = data_size - frame_vbo;
|
|
memcpy(frame_mem, resident_data(attr) + frame_vbo,
|
|
min(ondisk_size, frame_size));
|
|
}
|
|
err = 0;
|
|
goto out1;
|
|
}
|
|
|
|
if (frame_vbo >= valid_size) {
|
|
memset(frame_mem, 0, frame_size);
|
|
err = 0;
|
|
goto out1;
|
|
}
|
|
|
|
if (ni->ni_flags & NI_FLAG_COMPRESSED_MASK) {
|
|
#ifndef CONFIG_NTFS3_LZX_XPRESS
|
|
err = -EOPNOTSUPP;
|
|
goto out1;
|
|
#else
|
|
u32 frame_bits = ni_ext_compress_bits(ni);
|
|
u64 frame64 = frame_vbo >> frame_bits;
|
|
u64 frames, vbo_data;
|
|
|
|
if (frame_size != (1u << frame_bits)) {
|
|
err = -EINVAL;
|
|
goto out1;
|
|
}
|
|
switch (frame_size) {
|
|
case 0x1000:
|
|
case 0x2000:
|
|
case 0x4000:
|
|
case 0x8000:
|
|
break;
|
|
default:
|
|
/* Unknown compression. */
|
|
err = -EOPNOTSUPP;
|
|
goto out1;
|
|
}
|
|
|
|
attr = ni_find_attr(ni, attr, &le, ATTR_DATA, WOF_NAME,
|
|
ARRAY_SIZE(WOF_NAME), NULL, NULL);
|
|
if (!attr) {
|
|
ntfs_inode_err(
|
|
&ni->vfs_inode,
|
|
"external compressed file should contains data attribute \"WofCompressedData\"");
|
|
err = -EINVAL;
|
|
goto out1;
|
|
}
|
|
|
|
if (!attr->non_res) {
|
|
run = NULL;
|
|
} else {
|
|
run = run_alloc();
|
|
if (!run) {
|
|
err = -ENOMEM;
|
|
goto out1;
|
|
}
|
|
}
|
|
|
|
frames = (ni->vfs_inode.i_size - 1) >> frame_bits;
|
|
|
|
err = attr_wof_frame_info(ni, attr, run, frame64, frames,
|
|
frame_bits, &ondisk_size, &vbo_data);
|
|
if (err)
|
|
goto out2;
|
|
|
|
if (frame64 == frames) {
|
|
unc_size = 1 + ((ni->vfs_inode.i_size - 1) &
|
|
(frame_size - 1));
|
|
ondisk_size = attr_size(attr) - vbo_data;
|
|
} else {
|
|
unc_size = frame_size;
|
|
}
|
|
|
|
if (ondisk_size > frame_size) {
|
|
err = -EINVAL;
|
|
goto out2;
|
|
}
|
|
|
|
if (!attr->non_res) {
|
|
if (vbo_data + ondisk_size >
|
|
le32_to_cpu(attr->res.data_size)) {
|
|
err = -EINVAL;
|
|
goto out1;
|
|
}
|
|
|
|
err = decompress_lzx_xpress(
|
|
sbi, Add2Ptr(resident_data(attr), vbo_data),
|
|
ondisk_size, frame_mem, unc_size, frame_size);
|
|
goto out1;
|
|
}
|
|
vbo_disk = vbo_data;
|
|
/* Load all runs to read [vbo_disk-vbo_to). */
|
|
err = attr_load_runs_range(ni, ATTR_DATA, WOF_NAME,
|
|
ARRAY_SIZE(WOF_NAME), run, vbo_disk,
|
|
vbo_data + ondisk_size);
|
|
if (err)
|
|
goto out2;
|
|
npages_disk = (ondisk_size + (vbo_disk & (PAGE_SIZE - 1)) +
|
|
PAGE_SIZE - 1) >>
|
|
PAGE_SHIFT;
|
|
#endif
|
|
} else if (is_attr_compressed(attr)) {
|
|
/* LZNT compression. */
|
|
if (sbi->cluster_size > NTFS_LZNT_MAX_CLUSTER) {
|
|
err = -EOPNOTSUPP;
|
|
goto out1;
|
|
}
|
|
|
|
if (attr->nres.c_unit != NTFS_LZNT_CUNIT) {
|
|
err = -EOPNOTSUPP;
|
|
goto out1;
|
|
}
|
|
|
|
down_write(&ni->file.run_lock);
|
|
run_truncate_around(run, le64_to_cpu(attr->nres.svcn));
|
|
frame = frame_vbo >> (cluster_bits + NTFS_LZNT_CUNIT);
|
|
err = attr_is_frame_compressed(ni, attr, frame, &clst_data);
|
|
up_write(&ni->file.run_lock);
|
|
if (err)
|
|
goto out1;
|
|
|
|
if (!clst_data) {
|
|
memset(frame_mem, 0, frame_size);
|
|
goto out1;
|
|
}
|
|
|
|
frame_size = sbi->cluster_size << NTFS_LZNT_CUNIT;
|
|
ondisk_size = clst_data << cluster_bits;
|
|
|
|
if (clst_data >= NTFS_LZNT_CLUSTERS) {
|
|
/* Frame is not compressed. */
|
|
down_read(&ni->file.run_lock);
|
|
err = ntfs_bio_pages(sbi, run, pages, pages_per_frame,
|
|
frame_vbo, ondisk_size,
|
|
REQ_OP_READ);
|
|
up_read(&ni->file.run_lock);
|
|
goto out1;
|
|
}
|
|
vbo_disk = frame_vbo;
|
|
npages_disk = (ondisk_size + PAGE_SIZE - 1) >> PAGE_SHIFT;
|
|
} else {
|
|
__builtin_unreachable();
|
|
err = -EINVAL;
|
|
goto out1;
|
|
}
|
|
|
|
pages_disk = kzalloc(npages_disk * sizeof(struct page *), GFP_NOFS);
|
|
if (!pages_disk) {
|
|
err = -ENOMEM;
|
|
goto out2;
|
|
}
|
|
|
|
for (i = 0; i < npages_disk; i++) {
|
|
pg = alloc_page(GFP_KERNEL);
|
|
if (!pg) {
|
|
err = -ENOMEM;
|
|
goto out3;
|
|
}
|
|
pages_disk[i] = pg;
|
|
lock_page(pg);
|
|
kmap(pg);
|
|
}
|
|
|
|
/* Read 'ondisk_size' bytes from disk. */
|
|
down_read(&ni->file.run_lock);
|
|
err = ntfs_bio_pages(sbi, run, pages_disk, npages_disk, vbo_disk,
|
|
ondisk_size, REQ_OP_READ);
|
|
up_read(&ni->file.run_lock);
|
|
if (err)
|
|
goto out3;
|
|
|
|
/*
|
|
* To simplify decompress algorithm do vmap for source and target pages.
|
|
*/
|
|
frame_ondisk = vmap(pages_disk, npages_disk, VM_MAP, PAGE_KERNEL_RO);
|
|
if (!frame_ondisk) {
|
|
err = -ENOMEM;
|
|
goto out3;
|
|
}
|
|
|
|
/* Decompress: Frame_ondisk -> frame_mem. */
|
|
#ifdef CONFIG_NTFS3_LZX_XPRESS
|
|
if (run != &ni->file.run) {
|
|
/* LZX or XPRESS */
|
|
err = decompress_lzx_xpress(
|
|
sbi, frame_ondisk + (vbo_disk & (PAGE_SIZE - 1)),
|
|
ondisk_size, frame_mem, unc_size, frame_size);
|
|
} else
|
|
#endif
|
|
{
|
|
/* LZNT - Native NTFS compression. */
|
|
unc_size = decompress_lznt(frame_ondisk, ondisk_size, frame_mem,
|
|
frame_size);
|
|
if ((ssize_t)unc_size < 0)
|
|
err = unc_size;
|
|
else if (!unc_size || unc_size > frame_size)
|
|
err = -EINVAL;
|
|
}
|
|
if (!err && valid_size < frame_vbo + frame_size) {
|
|
size_t ok = valid_size - frame_vbo;
|
|
|
|
memset(frame_mem + ok, 0, frame_size - ok);
|
|
}
|
|
|
|
vunmap(frame_ondisk);
|
|
|
|
out3:
|
|
for (i = 0; i < npages_disk; i++) {
|
|
pg = pages_disk[i];
|
|
if (pg) {
|
|
kunmap(pg);
|
|
unlock_page(pg);
|
|
put_page(pg);
|
|
}
|
|
}
|
|
kfree(pages_disk);
|
|
|
|
out2:
|
|
#ifdef CONFIG_NTFS3_LZX_XPRESS
|
|
if (run != &ni->file.run)
|
|
run_free(run);
|
|
#endif
|
|
out1:
|
|
vunmap(frame_mem);
|
|
out:
|
|
for (i = 0; i < pages_per_frame; i++) {
|
|
pg = pages[i];
|
|
kunmap(pg);
|
|
ClearPageError(pg);
|
|
SetPageUptodate(pg);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* ni_write_frame
|
|
*
|
|
* Pages - Array of locked pages.
|
|
*/
|
|
int ni_write_frame(struct ntfs_inode *ni, struct page **pages,
|
|
u32 pages_per_frame)
|
|
{
|
|
int err;
|
|
struct ntfs_sb_info *sbi = ni->mi.sbi;
|
|
u8 frame_bits = NTFS_LZNT_CUNIT + sbi->cluster_bits;
|
|
u32 frame_size = sbi->cluster_size << NTFS_LZNT_CUNIT;
|
|
u64 frame_vbo = (u64)pages[0]->index << PAGE_SHIFT;
|
|
CLST frame = frame_vbo >> frame_bits;
|
|
char *frame_ondisk = NULL;
|
|
struct page **pages_disk = NULL;
|
|
struct ATTR_LIST_ENTRY *le = NULL;
|
|
char *frame_mem;
|
|
struct ATTRIB *attr;
|
|
struct mft_inode *mi;
|
|
u32 i;
|
|
struct page *pg;
|
|
size_t compr_size, ondisk_size;
|
|
struct lznt *lznt;
|
|
|
|
attr = ni_find_attr(ni, NULL, &le, ATTR_DATA, NULL, 0, NULL, &mi);
|
|
if (!attr) {
|
|
err = -ENOENT;
|
|
goto out;
|
|
}
|
|
|
|
if (WARN_ON(!is_attr_compressed(attr))) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if (sbi->cluster_size > NTFS_LZNT_MAX_CLUSTER) {
|
|
err = -EOPNOTSUPP;
|
|
goto out;
|
|
}
|
|
|
|
if (!attr->non_res) {
|
|
down_write(&ni->file.run_lock);
|
|
err = attr_make_nonresident(ni, attr, le, mi,
|
|
le32_to_cpu(attr->res.data_size),
|
|
&ni->file.run, &attr, pages[0]);
|
|
up_write(&ni->file.run_lock);
|
|
if (err)
|
|
goto out;
|
|
}
|
|
|
|
if (attr->nres.c_unit != NTFS_LZNT_CUNIT) {
|
|
err = -EOPNOTSUPP;
|
|
goto out;
|
|
}
|
|
|
|
pages_disk = kcalloc(pages_per_frame, sizeof(struct page *), GFP_NOFS);
|
|
if (!pages_disk) {
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < pages_per_frame; i++) {
|
|
pg = alloc_page(GFP_KERNEL);
|
|
if (!pg) {
|
|
err = -ENOMEM;
|
|
goto out1;
|
|
}
|
|
pages_disk[i] = pg;
|
|
lock_page(pg);
|
|
kmap(pg);
|
|
}
|
|
|
|
/* To simplify compress algorithm do vmap for source and target pages. */
|
|
frame_ondisk = vmap(pages_disk, pages_per_frame, VM_MAP, PAGE_KERNEL);
|
|
if (!frame_ondisk) {
|
|
err = -ENOMEM;
|
|
goto out1;
|
|
}
|
|
|
|
for (i = 0; i < pages_per_frame; i++)
|
|
kmap(pages[i]);
|
|
|
|
/* Map in-memory frame for read-only. */
|
|
frame_mem = vmap(pages, pages_per_frame, VM_MAP, PAGE_KERNEL_RO);
|
|
if (!frame_mem) {
|
|
err = -ENOMEM;
|
|
goto out2;
|
|
}
|
|
|
|
mutex_lock(&sbi->compress.mtx_lznt);
|
|
lznt = NULL;
|
|
if (!sbi->compress.lznt) {
|
|
/*
|
|
* LZNT implements two levels of compression:
|
|
* 0 - Standard compression
|
|
* 1 - Best compression, requires a lot of cpu
|
|
* use mount option?
|
|
*/
|
|
lznt = get_lznt_ctx(0);
|
|
if (!lznt) {
|
|
mutex_unlock(&sbi->compress.mtx_lznt);
|
|
err = -ENOMEM;
|
|
goto out3;
|
|
}
|
|
|
|
sbi->compress.lznt = lznt;
|
|
lznt = NULL;
|
|
}
|
|
|
|
/* Compress: frame_mem -> frame_ondisk */
|
|
compr_size = compress_lznt(frame_mem, frame_size, frame_ondisk,
|
|
frame_size, sbi->compress.lznt);
|
|
mutex_unlock(&sbi->compress.mtx_lznt);
|
|
kfree(lznt);
|
|
|
|
if (compr_size + sbi->cluster_size > frame_size) {
|
|
/* Frame is not compressed. */
|
|
compr_size = frame_size;
|
|
ondisk_size = frame_size;
|
|
} else if (compr_size) {
|
|
/* Frame is compressed. */
|
|
ondisk_size = ntfs_up_cluster(sbi, compr_size);
|
|
memset(frame_ondisk + compr_size, 0, ondisk_size - compr_size);
|
|
} else {
|
|
/* Frame is sparsed. */
|
|
ondisk_size = 0;
|
|
}
|
|
|
|
down_write(&ni->file.run_lock);
|
|
run_truncate_around(&ni->file.run, le64_to_cpu(attr->nres.svcn));
|
|
err = attr_allocate_frame(ni, frame, compr_size, ni->i_valid);
|
|
up_write(&ni->file.run_lock);
|
|
if (err)
|
|
goto out2;
|
|
|
|
if (!ondisk_size)
|
|
goto out2;
|
|
|
|
down_read(&ni->file.run_lock);
|
|
err = ntfs_bio_pages(sbi, &ni->file.run,
|
|
ondisk_size < frame_size ? pages_disk : pages,
|
|
pages_per_frame, frame_vbo, ondisk_size,
|
|
REQ_OP_WRITE);
|
|
up_read(&ni->file.run_lock);
|
|
|
|
out3:
|
|
vunmap(frame_mem);
|
|
|
|
out2:
|
|
for (i = 0; i < pages_per_frame; i++)
|
|
kunmap(pages[i]);
|
|
|
|
vunmap(frame_ondisk);
|
|
out1:
|
|
for (i = 0; i < pages_per_frame; i++) {
|
|
pg = pages_disk[i];
|
|
if (pg) {
|
|
kunmap(pg);
|
|
unlock_page(pg);
|
|
put_page(pg);
|
|
}
|
|
}
|
|
kfree(pages_disk);
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* ni_remove_name - Removes name 'de' from MFT and from directory.
|
|
* 'de2' and 'undo_step' are used to restore MFT/dir, if error occurs.
|
|
*/
|
|
int ni_remove_name(struct ntfs_inode *dir_ni, struct ntfs_inode *ni,
|
|
struct NTFS_DE *de, struct NTFS_DE **de2, int *undo_step)
|
|
{
|
|
int err;
|
|
struct ntfs_sb_info *sbi = ni->mi.sbi;
|
|
struct ATTR_FILE_NAME *de_name = (struct ATTR_FILE_NAME *)(de + 1);
|
|
struct ATTR_FILE_NAME *fname;
|
|
struct ATTR_LIST_ENTRY *le;
|
|
struct mft_inode *mi;
|
|
u16 de_key_size = le16_to_cpu(de->key_size);
|
|
u8 name_type;
|
|
|
|
*undo_step = 0;
|
|
|
|
/* Find name in record. */
|
|
mi_get_ref(&dir_ni->mi, &de_name->home);
|
|
|
|
fname = ni_fname_name(ni, (struct le_str *)&de_name->name_len,
|
|
&de_name->home, &mi, &le);
|
|
if (!fname)
|
|
return -ENOENT;
|
|
|
|
memcpy(&de_name->dup, &fname->dup, sizeof(struct NTFS_DUP_INFO));
|
|
name_type = paired_name(fname->type);
|
|
|
|
/* Mark ntfs as dirty. It will be cleared at umount. */
|
|
ntfs_set_state(sbi, NTFS_DIRTY_DIRTY);
|
|
|
|
/* Step 1: Remove name from directory. */
|
|
err = indx_delete_entry(&dir_ni->dir, dir_ni, fname, de_key_size, sbi);
|
|
if (err)
|
|
return err;
|
|
|
|
/* Step 2: Remove name from MFT. */
|
|
ni_remove_attr_le(ni, attr_from_name(fname), mi, le);
|
|
|
|
*undo_step = 2;
|
|
|
|
/* Get paired name. */
|
|
fname = ni_fname_type(ni, name_type, &mi, &le);
|
|
if (fname) {
|
|
u16 de2_key_size = fname_full_size(fname);
|
|
|
|
*de2 = Add2Ptr(de, 1024);
|
|
(*de2)->key_size = cpu_to_le16(de2_key_size);
|
|
|
|
memcpy(*de2 + 1, fname, de2_key_size);
|
|
|
|
/* Step 3: Remove paired name from directory. */
|
|
err = indx_delete_entry(&dir_ni->dir, dir_ni, fname,
|
|
de2_key_size, sbi);
|
|
if (err)
|
|
return err;
|
|
|
|
/* Step 4: Remove paired name from MFT. */
|
|
ni_remove_attr_le(ni, attr_from_name(fname), mi, le);
|
|
|
|
*undo_step = 4;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* ni_remove_name_undo - Paired function for ni_remove_name.
|
|
*
|
|
* Return: True if ok
|
|
*/
|
|
bool ni_remove_name_undo(struct ntfs_inode *dir_ni, struct ntfs_inode *ni,
|
|
struct NTFS_DE *de, struct NTFS_DE *de2, int undo_step)
|
|
{
|
|
struct ntfs_sb_info *sbi = ni->mi.sbi;
|
|
struct ATTRIB *attr;
|
|
u16 de_key_size;
|
|
|
|
switch (undo_step) {
|
|
case 4:
|
|
de_key_size = le16_to_cpu(de2->key_size);
|
|
if (ni_insert_resident(ni, de_key_size, ATTR_NAME, NULL, 0,
|
|
&attr, NULL, NULL))
|
|
return false;
|
|
memcpy(Add2Ptr(attr, SIZEOF_RESIDENT), de2 + 1, de_key_size);
|
|
|
|
mi_get_ref(&ni->mi, &de2->ref);
|
|
de2->size = cpu_to_le16(ALIGN(de_key_size, 8) +
|
|
sizeof(struct NTFS_DE));
|
|
de2->flags = 0;
|
|
de2->res = 0;
|
|
|
|
if (indx_insert_entry(&dir_ni->dir, dir_ni, de2, sbi, NULL, 1))
|
|
return false;
|
|
fallthrough;
|
|
|
|
case 2:
|
|
de_key_size = le16_to_cpu(de->key_size);
|
|
|
|
if (ni_insert_resident(ni, de_key_size, ATTR_NAME, NULL, 0,
|
|
&attr, NULL, NULL))
|
|
return false;
|
|
|
|
memcpy(Add2Ptr(attr, SIZEOF_RESIDENT), de + 1, de_key_size);
|
|
mi_get_ref(&ni->mi, &de->ref);
|
|
|
|
if (indx_insert_entry(&dir_ni->dir, dir_ni, de, sbi, NULL, 1))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* ni_add_name - Add new name into MFT and into directory.
|
|
*/
|
|
int ni_add_name(struct ntfs_inode *dir_ni, struct ntfs_inode *ni,
|
|
struct NTFS_DE *de)
|
|
{
|
|
int err;
|
|
struct ntfs_sb_info *sbi = ni->mi.sbi;
|
|
struct ATTRIB *attr;
|
|
struct ATTR_LIST_ENTRY *le;
|
|
struct mft_inode *mi;
|
|
struct ATTR_FILE_NAME *fname;
|
|
struct ATTR_FILE_NAME *de_name = (struct ATTR_FILE_NAME *)(de + 1);
|
|
u16 de_key_size = le16_to_cpu(de->key_size);
|
|
|
|
if (sbi->options->windows_names &&
|
|
!valid_windows_name(sbi, (struct le_str *)&de_name->name_len))
|
|
return -EINVAL;
|
|
|
|
/* If option "hide_dot_files" then set hidden attribute for dot files. */
|
|
if (ni->mi.sbi->options->hide_dot_files) {
|
|
if (de_name->name_len > 0 &&
|
|
le16_to_cpu(de_name->name[0]) == '.')
|
|
ni->std_fa |= FILE_ATTRIBUTE_HIDDEN;
|
|
else
|
|
ni->std_fa &= ~FILE_ATTRIBUTE_HIDDEN;
|
|
}
|
|
|
|
mi_get_ref(&ni->mi, &de->ref);
|
|
mi_get_ref(&dir_ni->mi, &de_name->home);
|
|
|
|
/* Fill duplicate from any ATTR_NAME. */
|
|
fname = ni_fname_name(ni, NULL, NULL, NULL, NULL);
|
|
if (fname)
|
|
memcpy(&de_name->dup, &fname->dup, sizeof(fname->dup));
|
|
de_name->dup.fa = ni->std_fa;
|
|
|
|
/* Insert new name into MFT. */
|
|
err = ni_insert_resident(ni, de_key_size, ATTR_NAME, NULL, 0, &attr,
|
|
&mi, &le);
|
|
if (err)
|
|
return err;
|
|
|
|
memcpy(Add2Ptr(attr, SIZEOF_RESIDENT), de_name, de_key_size);
|
|
|
|
/* Insert new name into directory. */
|
|
err = indx_insert_entry(&dir_ni->dir, dir_ni, de, sbi, NULL, 0);
|
|
if (err)
|
|
ni_remove_attr_le(ni, attr, mi, le);
|
|
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* ni_rename - Remove one name and insert new name.
|
|
*/
|
|
int ni_rename(struct ntfs_inode *dir_ni, struct ntfs_inode *new_dir_ni,
|
|
struct ntfs_inode *ni, struct NTFS_DE *de, struct NTFS_DE *new_de,
|
|
bool *is_bad)
|
|
{
|
|
int err;
|
|
struct NTFS_DE *de2 = NULL;
|
|
int undo = 0;
|
|
|
|
/*
|
|
* There are two possible ways to rename:
|
|
* 1) Add new name and remove old name.
|
|
* 2) Remove old name and add new name.
|
|
*
|
|
* In most cases (not all!) adding new name into MFT and into directory can
|
|
* allocate additional cluster(s).
|
|
* Second way may result to bad inode if we can't add new name
|
|
* and then can't restore (add) old name.
|
|
*/
|
|
|
|
/*
|
|
* Way 1 - Add new + remove old.
|
|
*/
|
|
err = ni_add_name(new_dir_ni, ni, new_de);
|
|
if (!err) {
|
|
err = ni_remove_name(dir_ni, ni, de, &de2, &undo);
|
|
if (err && ni_remove_name(new_dir_ni, ni, new_de, &de2, &undo))
|
|
*is_bad = true;
|
|
}
|
|
|
|
/*
|
|
* Way 2 - Remove old + add new.
|
|
*/
|
|
/*
|
|
* err = ni_remove_name(dir_ni, ni, de, &de2, &undo);
|
|
* if (!err) {
|
|
* err = ni_add_name(new_dir_ni, ni, new_de);
|
|
* if (err && !ni_remove_name_undo(dir_ni, ni, de, de2, undo))
|
|
* *is_bad = true;
|
|
* }
|
|
*/
|
|
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* ni_is_dirty - Return: True if 'ni' requires ni_write_inode.
|
|
*/
|
|
bool ni_is_dirty(struct inode *inode)
|
|
{
|
|
struct ntfs_inode *ni = ntfs_i(inode);
|
|
struct rb_node *node;
|
|
|
|
if (ni->mi.dirty || ni->attr_list.dirty ||
|
|
(ni->ni_flags & NI_FLAG_UPDATE_PARENT))
|
|
return true;
|
|
|
|
for (node = rb_first(&ni->mi_tree); node; node = rb_next(node)) {
|
|
if (rb_entry(node, struct mft_inode, node)->dirty)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* ni_update_parent
|
|
*
|
|
* Update duplicate info of ATTR_FILE_NAME in MFT and in parent directories.
|
|
*/
|
|
static bool ni_update_parent(struct ntfs_inode *ni, struct NTFS_DUP_INFO *dup,
|
|
int sync)
|
|
{
|
|
struct ATTRIB *attr;
|
|
struct mft_inode *mi;
|
|
struct ATTR_LIST_ENTRY *le = NULL;
|
|
struct ntfs_sb_info *sbi = ni->mi.sbi;
|
|
struct super_block *sb = sbi->sb;
|
|
bool re_dirty = false;
|
|
|
|
if (ni->mi.mrec->flags & RECORD_FLAG_DIR) {
|
|
dup->fa |= FILE_ATTRIBUTE_DIRECTORY;
|
|
attr = NULL;
|
|
dup->alloc_size = 0;
|
|
dup->data_size = 0;
|
|
} else {
|
|
dup->fa &= ~FILE_ATTRIBUTE_DIRECTORY;
|
|
|
|
attr = ni_find_attr(ni, NULL, &le, ATTR_DATA, NULL, 0, NULL,
|
|
&mi);
|
|
if (!attr) {
|
|
dup->alloc_size = dup->data_size = 0;
|
|
} else if (!attr->non_res) {
|
|
u32 data_size = le32_to_cpu(attr->res.data_size);
|
|
|
|
dup->alloc_size = cpu_to_le64(ALIGN(data_size, 8));
|
|
dup->data_size = cpu_to_le64(data_size);
|
|
} else {
|
|
u64 new_valid = ni->i_valid;
|
|
u64 data_size = le64_to_cpu(attr->nres.data_size);
|
|
__le64 valid_le;
|
|
|
|
dup->alloc_size = is_attr_ext(attr) ?
|
|
attr->nres.total_size :
|
|
attr->nres.alloc_size;
|
|
dup->data_size = attr->nres.data_size;
|
|
|
|
if (new_valid > data_size)
|
|
new_valid = data_size;
|
|
|
|
valid_le = cpu_to_le64(new_valid);
|
|
if (valid_le != attr->nres.valid_size) {
|
|
attr->nres.valid_size = valid_le;
|
|
mi->dirty = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* TODO: Fill reparse info. */
|
|
dup->reparse = 0;
|
|
dup->ea_size = 0;
|
|
|
|
if (ni->ni_flags & NI_FLAG_EA) {
|
|
attr = ni_find_attr(ni, attr, &le, ATTR_EA_INFO, NULL, 0, NULL,
|
|
NULL);
|
|
if (attr) {
|
|
const struct EA_INFO *info;
|
|
|
|
info = resident_data_ex(attr, sizeof(struct EA_INFO));
|
|
/* If ATTR_EA_INFO exists 'info' can't be NULL. */
|
|
if (info)
|
|
dup->ea_size = info->size_pack;
|
|
}
|
|
}
|
|
|
|
attr = NULL;
|
|
le = NULL;
|
|
|
|
while ((attr = ni_find_attr(ni, attr, &le, ATTR_NAME, NULL, 0, NULL,
|
|
&mi))) {
|
|
struct inode *dir;
|
|
struct ATTR_FILE_NAME *fname;
|
|
|
|
fname = resident_data_ex(attr, SIZEOF_ATTRIBUTE_FILENAME);
|
|
if (!fname || !memcmp(&fname->dup, dup, sizeof(fname->dup)))
|
|
continue;
|
|
|
|
/* Check simple case when parent inode equals current inode. */
|
|
if (ino_get(&fname->home) == ni->vfs_inode.i_ino) {
|
|
ntfs_set_state(sbi, NTFS_DIRTY_ERROR);
|
|
continue;
|
|
}
|
|
|
|
/* ntfs_iget5 may sleep. */
|
|
dir = ntfs_iget5(sb, &fname->home, NULL);
|
|
if (IS_ERR(dir)) {
|
|
ntfs_inode_warn(
|
|
&ni->vfs_inode,
|
|
"failed to open parent directory r=%lx to update",
|
|
(long)ino_get(&fname->home));
|
|
continue;
|
|
}
|
|
|
|
if (!is_bad_inode(dir)) {
|
|
struct ntfs_inode *dir_ni = ntfs_i(dir);
|
|
|
|
if (!ni_trylock(dir_ni)) {
|
|
re_dirty = true;
|
|
} else {
|
|
indx_update_dup(dir_ni, sbi, fname, dup, sync);
|
|
ni_unlock(dir_ni);
|
|
memcpy(&fname->dup, dup, sizeof(fname->dup));
|
|
mi->dirty = true;
|
|
}
|
|
}
|
|
iput(dir);
|
|
}
|
|
|
|
return re_dirty;
|
|
}
|
|
|
|
/*
|
|
* ni_write_inode - Write MFT base record and all subrecords to disk.
|
|
*/
|
|
int ni_write_inode(struct inode *inode, int sync, const char *hint)
|
|
{
|
|
int err = 0, err2;
|
|
struct ntfs_inode *ni = ntfs_i(inode);
|
|
struct super_block *sb = inode->i_sb;
|
|
struct ntfs_sb_info *sbi = sb->s_fs_info;
|
|
bool re_dirty = false;
|
|
struct ATTR_STD_INFO *std;
|
|
struct rb_node *node, *next;
|
|
struct NTFS_DUP_INFO dup;
|
|
|
|
if (is_bad_inode(inode) || sb_rdonly(sb))
|
|
return 0;
|
|
|
|
if (!ni_trylock(ni)) {
|
|
/* 'ni' is under modification, skip for now. */
|
|
mark_inode_dirty_sync(inode);
|
|
return 0;
|
|
}
|
|
|
|
if (!ni->mi.mrec)
|
|
goto out;
|
|
|
|
if (is_rec_inuse(ni->mi.mrec) &&
|
|
!(sbi->flags & NTFS_FLAGS_LOG_REPLAYING) && inode->i_nlink) {
|
|
bool modified = false;
|
|
struct timespec64 ts;
|
|
|
|
/* Update times in standard attribute. */
|
|
std = ni_std(ni);
|
|
if (!std) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
/* Update the access times if they have changed. */
|
|
ts = inode_get_mtime(inode);
|
|
dup.m_time = kernel2nt(&ts);
|
|
if (std->m_time != dup.m_time) {
|
|
std->m_time = dup.m_time;
|
|
modified = true;
|
|
}
|
|
|
|
ts = inode_get_mtime(inode);
|
|
dup.c_time = kernel2nt(&ts);
|
|
if (std->c_time != dup.c_time) {
|
|
std->c_time = dup.c_time;
|
|
modified = true;
|
|
}
|
|
|
|
ts = inode_get_atime(inode);
|
|
dup.a_time = kernel2nt(&ts);
|
|
if (std->a_time != dup.a_time) {
|
|
std->a_time = dup.a_time;
|
|
modified = true;
|
|
}
|
|
|
|
dup.fa = ni->std_fa;
|
|
if (std->fa != dup.fa) {
|
|
std->fa = dup.fa;
|
|
modified = true;
|
|
}
|
|
|
|
/* std attribute is always in primary MFT record. */
|
|
if (modified)
|
|
ni->mi.dirty = true;
|
|
|
|
if (!ntfs_is_meta_file(sbi, inode->i_ino) &&
|
|
(modified || (ni->ni_flags & NI_FLAG_UPDATE_PARENT))
|
|
/* Avoid __wait_on_freeing_inode(inode). */
|
|
&& (sb->s_flags & SB_ACTIVE)) {
|
|
dup.cr_time = std->cr_time;
|
|
/* Not critical if this function fail. */
|
|
re_dirty = ni_update_parent(ni, &dup, sync);
|
|
|
|
if (re_dirty)
|
|
ni->ni_flags |= NI_FLAG_UPDATE_PARENT;
|
|
else
|
|
ni->ni_flags &= ~NI_FLAG_UPDATE_PARENT;
|
|
}
|
|
|
|
/* Update attribute list. */
|
|
if (ni->attr_list.size && ni->attr_list.dirty) {
|
|
if (inode->i_ino != MFT_REC_MFT || sync) {
|
|
err = ni_try_remove_attr_list(ni);
|
|
if (err)
|
|
goto out;
|
|
}
|
|
|
|
err = al_update(ni, sync);
|
|
if (err)
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
for (node = rb_first(&ni->mi_tree); node; node = next) {
|
|
struct mft_inode *mi = rb_entry(node, struct mft_inode, node);
|
|
bool is_empty;
|
|
|
|
next = rb_next(node);
|
|
|
|
if (!mi->dirty)
|
|
continue;
|
|
|
|
is_empty = !mi_enum_attr(mi, NULL);
|
|
|
|
if (is_empty)
|
|
clear_rec_inuse(mi->mrec);
|
|
|
|
err2 = mi_write(mi, sync);
|
|
if (!err && err2)
|
|
err = err2;
|
|
|
|
if (is_empty) {
|
|
ntfs_mark_rec_free(sbi, mi->rno, false);
|
|
rb_erase(node, &ni->mi_tree);
|
|
mi_put(mi);
|
|
}
|
|
}
|
|
|
|
if (ni->mi.dirty) {
|
|
err2 = mi_write(&ni->mi, sync);
|
|
if (!err && err2)
|
|
err = err2;
|
|
}
|
|
out:
|
|
ni_unlock(ni);
|
|
|
|
if (err) {
|
|
ntfs_inode_err(inode, "%s failed, %d.", hint, err);
|
|
ntfs_set_state(sbi, NTFS_DIRTY_ERROR);
|
|
return err;
|
|
}
|
|
|
|
if (re_dirty)
|
|
mark_inode_dirty_sync(inode);
|
|
|
|
return 0;
|
|
}
|