Problem with these wrapper is that we cannot take off example GFP_NOFS flag. It is not recomended use those in all places. Also if we change one driver specific wrapper to kernel wrapper then it would look really weird. People should be most familiar with kernel wrappers so let's just use those ones. Driver specific alloc wrapper also confuse some static analyzing tools, good example is example kernels checkpatch tool. After we converter these to kernel specific then warnings is showed. Following Coccinelle script was used to automate changing. virtual patch @alloc depends on patch@ expression x; expression y; @@ ( - ntfs_malloc(x) + kmalloc(x, GFP_NOFS) | - ntfs_zalloc(x) + kzalloc(x, GFP_NOFS) | - ntfs_vmalloc(x) + kvmalloc(x, GFP_NOFS) | - ntfs_free(x) + kfree(x) | - ntfs_vfree(x) + kvfree(x) | - ntfs_memdup(x, y) + kmemdup(x, y, GFP_NOFS) ) Reviewed-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Kari Argillander <kari.argillander@gmail.com> Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
457 lines
9.2 KiB
C
457 lines
9.2 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
*
|
|
* Copyright (C) 2019-2021 Paragon Software GmbH, All rights reserved.
|
|
*
|
|
*/
|
|
|
|
#include <linux/blkdev.h>
|
|
#include <linux/buffer_head.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/nls.h>
|
|
|
|
#include "debug.h"
|
|
#include "ntfs.h"
|
|
#include "ntfs_fs.h"
|
|
|
|
/* Returns true if le is valid */
|
|
static inline bool al_is_valid_le(const struct ntfs_inode *ni,
|
|
struct ATTR_LIST_ENTRY *le)
|
|
{
|
|
if (!le || !ni->attr_list.le || !ni->attr_list.size)
|
|
return false;
|
|
|
|
return PtrOffset(ni->attr_list.le, le) + le16_to_cpu(le->size) <=
|
|
ni->attr_list.size;
|
|
}
|
|
|
|
void al_destroy(struct ntfs_inode *ni)
|
|
{
|
|
run_close(&ni->attr_list.run);
|
|
kfree(ni->attr_list.le);
|
|
ni->attr_list.le = NULL;
|
|
ni->attr_list.size = 0;
|
|
ni->attr_list.dirty = false;
|
|
}
|
|
|
|
/*
|
|
* ntfs_load_attr_list
|
|
*
|
|
* This method makes sure that the ATTRIB list, if present,
|
|
* has been properly set up.
|
|
*/
|
|
int ntfs_load_attr_list(struct ntfs_inode *ni, struct ATTRIB *attr)
|
|
{
|
|
int err;
|
|
size_t lsize;
|
|
void *le = NULL;
|
|
|
|
if (ni->attr_list.size)
|
|
return 0;
|
|
|
|
if (!attr->non_res) {
|
|
lsize = le32_to_cpu(attr->res.data_size);
|
|
le = kmalloc(al_aligned(lsize), GFP_NOFS);
|
|
if (!le) {
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
memcpy(le, resident_data(attr), lsize);
|
|
} else if (attr->nres.svcn) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
} else {
|
|
u16 run_off = le16_to_cpu(attr->nres.run_off);
|
|
|
|
lsize = le64_to_cpu(attr->nres.data_size);
|
|
|
|
run_init(&ni->attr_list.run);
|
|
|
|
err = run_unpack_ex(&ni->attr_list.run, ni->mi.sbi, ni->mi.rno,
|
|
0, le64_to_cpu(attr->nres.evcn), 0,
|
|
Add2Ptr(attr, run_off),
|
|
le32_to_cpu(attr->size) - run_off);
|
|
if (err < 0)
|
|
goto out;
|
|
|
|
le = kmalloc(al_aligned(lsize), GFP_NOFS);
|
|
if (!le) {
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
err = ntfs_read_run_nb(ni->mi.sbi, &ni->attr_list.run, 0, le,
|
|
lsize, NULL);
|
|
if (err)
|
|
goto out;
|
|
}
|
|
|
|
ni->attr_list.size = lsize;
|
|
ni->attr_list.le = le;
|
|
|
|
return 0;
|
|
|
|
out:
|
|
ni->attr_list.le = le;
|
|
al_destroy(ni);
|
|
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* al_enumerate
|
|
*
|
|
* Returns the next list 'le'
|
|
* if 'le' is NULL then returns the first 'le'
|
|
*/
|
|
struct ATTR_LIST_ENTRY *al_enumerate(struct ntfs_inode *ni,
|
|
struct ATTR_LIST_ENTRY *le)
|
|
{
|
|
size_t off;
|
|
u16 sz;
|
|
|
|
if (!le) {
|
|
le = ni->attr_list.le;
|
|
} else {
|
|
sz = le16_to_cpu(le->size);
|
|
if (sz < sizeof(struct ATTR_LIST_ENTRY)) {
|
|
/* Impossible 'cause we should not return such 'le' */
|
|
return NULL;
|
|
}
|
|
le = Add2Ptr(le, sz);
|
|
}
|
|
|
|
/* Check boundary */
|
|
off = PtrOffset(ni->attr_list.le, le);
|
|
if (off + sizeof(struct ATTR_LIST_ENTRY) > ni->attr_list.size) {
|
|
// The regular end of list
|
|
return NULL;
|
|
}
|
|
|
|
sz = le16_to_cpu(le->size);
|
|
|
|
/* Check 'le' for errors */
|
|
if (sz < sizeof(struct ATTR_LIST_ENTRY) ||
|
|
off + sz > ni->attr_list.size ||
|
|
sz < le->name_off + le->name_len * sizeof(short)) {
|
|
return NULL;
|
|
}
|
|
|
|
return le;
|
|
}
|
|
|
|
/*
|
|
* al_find_le
|
|
*
|
|
* finds the first 'le' in the list which matches type, name and vcn
|
|
* Returns NULL if not found
|
|
*/
|
|
struct ATTR_LIST_ENTRY *al_find_le(struct ntfs_inode *ni,
|
|
struct ATTR_LIST_ENTRY *le,
|
|
const struct ATTRIB *attr)
|
|
{
|
|
CLST svcn = attr_svcn(attr);
|
|
|
|
return al_find_ex(ni, le, attr->type, attr_name(attr), attr->name_len,
|
|
&svcn);
|
|
}
|
|
|
|
/*
|
|
* al_find_ex
|
|
*
|
|
* finds the first 'le' in the list which matches type, name and vcn
|
|
* Returns NULL if not found
|
|
*/
|
|
struct ATTR_LIST_ENTRY *al_find_ex(struct ntfs_inode *ni,
|
|
struct ATTR_LIST_ENTRY *le,
|
|
enum ATTR_TYPE type, const __le16 *name,
|
|
u8 name_len, const CLST *vcn)
|
|
{
|
|
struct ATTR_LIST_ENTRY *ret = NULL;
|
|
u32 type_in = le32_to_cpu(type);
|
|
|
|
while ((le = al_enumerate(ni, le))) {
|
|
u64 le_vcn;
|
|
int diff = le32_to_cpu(le->type) - type_in;
|
|
|
|
/* List entries are sorted by type, name and vcn */
|
|
if (diff < 0)
|
|
continue;
|
|
|
|
if (diff > 0)
|
|
return ret;
|
|
|
|
if (le->name_len != name_len)
|
|
continue;
|
|
|
|
le_vcn = le64_to_cpu(le->vcn);
|
|
if (!le_vcn) {
|
|
/*
|
|
* compare entry names only for entry with vcn == 0
|
|
*/
|
|
diff = ntfs_cmp_names(le_name(le), name_len, name,
|
|
name_len, ni->mi.sbi->upcase,
|
|
true);
|
|
if (diff < 0)
|
|
continue;
|
|
|
|
if (diff > 0)
|
|
return ret;
|
|
}
|
|
|
|
if (!vcn)
|
|
return le;
|
|
|
|
if (*vcn == le_vcn)
|
|
return le;
|
|
|
|
if (*vcn < le_vcn)
|
|
return ret;
|
|
|
|
ret = le;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* al_find_le_to_insert
|
|
*
|
|
* finds the first list entry which matches type, name and vcn
|
|
*/
|
|
static struct ATTR_LIST_ENTRY *al_find_le_to_insert(struct ntfs_inode *ni,
|
|
enum ATTR_TYPE type,
|
|
const __le16 *name,
|
|
u8 name_len, CLST vcn)
|
|
{
|
|
struct ATTR_LIST_ENTRY *le = NULL, *prev;
|
|
u32 type_in = le32_to_cpu(type);
|
|
|
|
/* List entries are sorted by type, name, vcn */
|
|
while ((le = al_enumerate(ni, prev = le))) {
|
|
int diff = le32_to_cpu(le->type) - type_in;
|
|
|
|
if (diff < 0)
|
|
continue;
|
|
|
|
if (diff > 0)
|
|
return le;
|
|
|
|
if (!le->vcn) {
|
|
/*
|
|
* compare entry names only for entry with vcn == 0
|
|
*/
|
|
diff = ntfs_cmp_names(le_name(le), le->name_len, name,
|
|
name_len, ni->mi.sbi->upcase,
|
|
true);
|
|
if (diff < 0)
|
|
continue;
|
|
|
|
if (diff > 0)
|
|
return le;
|
|
}
|
|
|
|
if (le64_to_cpu(le->vcn) >= vcn)
|
|
return le;
|
|
}
|
|
|
|
return prev ? Add2Ptr(prev, le16_to_cpu(prev->size)) : ni->attr_list.le;
|
|
}
|
|
|
|
/*
|
|
* al_add_le
|
|
*
|
|
* adds an "attribute list entry" to the list.
|
|
*/
|
|
int al_add_le(struct ntfs_inode *ni, enum ATTR_TYPE type, const __le16 *name,
|
|
u8 name_len, CLST svcn, __le16 id, const struct MFT_REF *ref,
|
|
struct ATTR_LIST_ENTRY **new_le)
|
|
{
|
|
int err;
|
|
struct ATTRIB *attr;
|
|
struct ATTR_LIST_ENTRY *le;
|
|
size_t off;
|
|
u16 sz;
|
|
size_t asize, new_asize;
|
|
u64 new_size;
|
|
typeof(ni->attr_list) *al = &ni->attr_list;
|
|
|
|
/*
|
|
* Compute the size of the new 'le'
|
|
*/
|
|
sz = le_size(name_len);
|
|
new_size = al->size + sz;
|
|
asize = al_aligned(al->size);
|
|
new_asize = al_aligned(new_size);
|
|
|
|
/* Scan forward to the point at which the new 'le' should be inserted. */
|
|
le = al_find_le_to_insert(ni, type, name, name_len, svcn);
|
|
off = PtrOffset(al->le, le);
|
|
|
|
if (new_size > asize) {
|
|
void *ptr = kmalloc(new_asize, GFP_NOFS);
|
|
|
|
if (!ptr)
|
|
return -ENOMEM;
|
|
|
|
memcpy(ptr, al->le, off);
|
|
memcpy(Add2Ptr(ptr, off + sz), le, al->size - off);
|
|
le = Add2Ptr(ptr, off);
|
|
kfree(al->le);
|
|
al->le = ptr;
|
|
} else {
|
|
memmove(Add2Ptr(le, sz), le, al->size - off);
|
|
}
|
|
|
|
al->size = new_size;
|
|
|
|
le->type = type;
|
|
le->size = cpu_to_le16(sz);
|
|
le->name_len = name_len;
|
|
le->name_off = offsetof(struct ATTR_LIST_ENTRY, name);
|
|
le->vcn = cpu_to_le64(svcn);
|
|
le->ref = *ref;
|
|
le->id = id;
|
|
memcpy(le->name, name, sizeof(short) * name_len);
|
|
|
|
al->dirty = true;
|
|
|
|
err = attr_set_size(ni, ATTR_LIST, NULL, 0, &al->run, new_size,
|
|
&new_size, true, &attr);
|
|
if (err)
|
|
return err;
|
|
|
|
if (attr && attr->non_res) {
|
|
err = ntfs_sb_write_run(ni->mi.sbi, &al->run, 0, al->le,
|
|
al->size);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
al->dirty = false;
|
|
*new_le = le;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* al_remove_le
|
|
*
|
|
* removes 'le' from attribute list
|
|
*/
|
|
bool al_remove_le(struct ntfs_inode *ni, struct ATTR_LIST_ENTRY *le)
|
|
{
|
|
u16 size;
|
|
size_t off;
|
|
typeof(ni->attr_list) *al = &ni->attr_list;
|
|
|
|
if (!al_is_valid_le(ni, le))
|
|
return false;
|
|
|
|
/* Save on stack the size of 'le' */
|
|
size = le16_to_cpu(le->size);
|
|
off = PtrOffset(al->le, le);
|
|
|
|
memmove(le, Add2Ptr(le, size), al->size - (off + size));
|
|
|
|
al->size -= size;
|
|
al->dirty = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* al_delete_le
|
|
*
|
|
* deletes from the list the first 'le' which matches its parameters.
|
|
*/
|
|
bool al_delete_le(struct ntfs_inode *ni, enum ATTR_TYPE type, CLST vcn,
|
|
const __le16 *name, size_t name_len,
|
|
const struct MFT_REF *ref)
|
|
{
|
|
u16 size;
|
|
struct ATTR_LIST_ENTRY *le;
|
|
size_t off;
|
|
typeof(ni->attr_list) *al = &ni->attr_list;
|
|
|
|
/* Scan forward to the first 'le' that matches the input */
|
|
le = al_find_ex(ni, NULL, type, name, name_len, &vcn);
|
|
if (!le)
|
|
return false;
|
|
|
|
off = PtrOffset(al->le, le);
|
|
|
|
next:
|
|
if (off >= al->size)
|
|
return false;
|
|
if (le->type != type)
|
|
return false;
|
|
if (le->name_len != name_len)
|
|
return false;
|
|
if (name_len && ntfs_cmp_names(le_name(le), name_len, name, name_len,
|
|
ni->mi.sbi->upcase, true))
|
|
return false;
|
|
if (le64_to_cpu(le->vcn) != vcn)
|
|
return false;
|
|
|
|
/*
|
|
* The caller specified a segment reference, so we have to
|
|
* scan through the matching entries until we find that segment
|
|
* reference or we run of matching entries.
|
|
*/
|
|
if (ref && memcmp(ref, &le->ref, sizeof(*ref))) {
|
|
off += le16_to_cpu(le->size);
|
|
le = Add2Ptr(al->le, off);
|
|
goto next;
|
|
}
|
|
|
|
/* Save on stack the size of 'le' */
|
|
size = le16_to_cpu(le->size);
|
|
/* Delete 'le'. */
|
|
memmove(le, Add2Ptr(le, size), al->size - (off + size));
|
|
|
|
al->size -= size;
|
|
al->dirty = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* al_update
|
|
*/
|
|
int al_update(struct ntfs_inode *ni)
|
|
{
|
|
int err;
|
|
struct ATTRIB *attr;
|
|
typeof(ni->attr_list) *al = &ni->attr_list;
|
|
|
|
if (!al->dirty || !al->size)
|
|
return 0;
|
|
|
|
/*
|
|
* attribute list increased on demand in al_add_le
|
|
* attribute list decreased here
|
|
*/
|
|
err = attr_set_size(ni, ATTR_LIST, NULL, 0, &al->run, al->size, NULL,
|
|
false, &attr);
|
|
if (err)
|
|
goto out;
|
|
|
|
if (!attr->non_res) {
|
|
memcpy(resident_data(attr), al->le, al->size);
|
|
} else {
|
|
err = ntfs_sb_write_run(ni->mi.sbi, &al->run, 0, al->le,
|
|
al->size);
|
|
if (err)
|
|
goto out;
|
|
|
|
attr->nres.valid_size = attr->nres.data_size;
|
|
}
|
|
|
|
ni->mi.dirty = true;
|
|
al->dirty = false;
|
|
|
|
out:
|
|
return err;
|
|
}
|