mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02:00 
			
		
		
		
	fs/ntfs3: Add attrib operations
This adds attrib operations Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
This commit is contained in:
		
							parent
							
								
									4342306f0f
								
							
						
					
					
						commit
						be71b5cba2
					
				
					 3 changed files with 3680 additions and 0 deletions
				
			
		
							
								
								
									
										2096
									
								
								fs/ntfs3/attrib.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2096
									
								
								fs/ntfs3/attrib.c
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										456
									
								
								fs/ntfs3/attrlist.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										456
									
								
								fs/ntfs3/attrlist.c
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,456 @@
 | 
			
		|||
// 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);
 | 
			
		||||
	ntfs_free(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 = ntfs_malloc(al_aligned(lsize));
 | 
			
		||||
		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 = ntfs_malloc(al_aligned(lsize));
 | 
			
		||||
		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 = ntfs_malloc(new_asize);
 | 
			
		||||
 | 
			
		||||
		if (!ptr)
 | 
			
		||||
			return -ENOMEM;
 | 
			
		||||
 | 
			
		||||
		memcpy(ptr, al->le, off);
 | 
			
		||||
		memcpy(Add2Ptr(ptr, off + sz), le, al->size - off);
 | 
			
		||||
		le = Add2Ptr(ptr, off);
 | 
			
		||||
		ntfs_free(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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1128
									
								
								fs/ntfs3/xattr.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1128
									
								
								fs/ntfs3/xattr.c
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
		Loading…
	
		Reference in a new issue