forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
441 lines
12 KiB
441 lines
12 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* Copyright (C) 2007 Oracle. All rights reserved. |
|
*/ |
|
|
|
#include "ctree.h" |
|
#include "disk-io.h" |
|
#include "transaction.h" |
|
|
|
/* |
|
* insert a name into a directory, doing overflow properly if there is a hash |
|
* collision. data_size indicates how big the item inserted should be. On |
|
* success a struct btrfs_dir_item pointer is returned, otherwise it is |
|
* an ERR_PTR. |
|
* |
|
* The name is not copied into the dir item, you have to do that yourself. |
|
*/ |
|
static struct btrfs_dir_item *insert_with_overflow(struct btrfs_trans_handle |
|
*trans, |
|
struct btrfs_root *root, |
|
struct btrfs_path *path, |
|
struct btrfs_key *cpu_key, |
|
u32 data_size, |
|
const char *name, |
|
int name_len) |
|
{ |
|
struct btrfs_fs_info *fs_info = root->fs_info; |
|
int ret; |
|
char *ptr; |
|
struct extent_buffer *leaf; |
|
|
|
ret = btrfs_insert_empty_item(trans, root, path, cpu_key, data_size); |
|
if (ret == -EEXIST) { |
|
struct btrfs_dir_item *di; |
|
di = btrfs_match_dir_item_name(fs_info, path, name, name_len); |
|
if (di) |
|
return ERR_PTR(-EEXIST); |
|
btrfs_extend_item(path, data_size); |
|
} else if (ret < 0) |
|
return ERR_PTR(ret); |
|
WARN_ON(ret > 0); |
|
leaf = path->nodes[0]; |
|
ptr = btrfs_item_ptr(leaf, path->slots[0], char); |
|
ASSERT(data_size <= btrfs_item_size(leaf, path->slots[0])); |
|
ptr += btrfs_item_size(leaf, path->slots[0]) - data_size; |
|
return (struct btrfs_dir_item *)ptr; |
|
} |
|
|
|
/* |
|
* xattrs work a lot like directories, this inserts an xattr item |
|
* into the tree |
|
*/ |
|
int btrfs_insert_xattr_item(struct btrfs_trans_handle *trans, |
|
struct btrfs_root *root, |
|
struct btrfs_path *path, u64 objectid, |
|
const char *name, u16 name_len, |
|
const void *data, u16 data_len) |
|
{ |
|
int ret = 0; |
|
struct btrfs_dir_item *dir_item; |
|
unsigned long name_ptr, data_ptr; |
|
struct btrfs_key key, location; |
|
struct btrfs_disk_key disk_key; |
|
struct extent_buffer *leaf; |
|
u32 data_size; |
|
|
|
if (name_len + data_len > BTRFS_MAX_XATTR_SIZE(root->fs_info)) |
|
return -ENOSPC; |
|
|
|
key.objectid = objectid; |
|
key.type = BTRFS_XATTR_ITEM_KEY; |
|
key.offset = btrfs_name_hash(name, name_len); |
|
|
|
data_size = sizeof(*dir_item) + name_len + data_len; |
|
dir_item = insert_with_overflow(trans, root, path, &key, data_size, |
|
name, name_len); |
|
if (IS_ERR(dir_item)) |
|
return PTR_ERR(dir_item); |
|
memset(&location, 0, sizeof(location)); |
|
|
|
leaf = path->nodes[0]; |
|
btrfs_cpu_key_to_disk(&disk_key, &location); |
|
btrfs_set_dir_item_key(leaf, dir_item, &disk_key); |
|
btrfs_set_dir_type(leaf, dir_item, BTRFS_FT_XATTR); |
|
btrfs_set_dir_name_len(leaf, dir_item, name_len); |
|
btrfs_set_dir_transid(leaf, dir_item, trans->transid); |
|
btrfs_set_dir_data_len(leaf, dir_item, data_len); |
|
name_ptr = (unsigned long)(dir_item + 1); |
|
data_ptr = (unsigned long)((char *)name_ptr + name_len); |
|
|
|
write_extent_buffer(leaf, name, name_ptr, name_len); |
|
write_extent_buffer(leaf, data, data_ptr, data_len); |
|
btrfs_mark_buffer_dirty(path->nodes[0]); |
|
|
|
return ret; |
|
} |
|
|
|
/* |
|
* insert a directory item in the tree, doing all the magic for |
|
* both indexes. 'dir' indicates which objectid to insert it into, |
|
* 'location' is the key to stuff into the directory item, 'type' is the |
|
* type of the inode we're pointing to, and 'index' is the sequence number |
|
* to use for the second index (if one is created). |
|
* Will return 0 or -ENOMEM |
|
*/ |
|
int btrfs_insert_dir_item(struct btrfs_trans_handle *trans, const char *name, |
|
int name_len, struct btrfs_inode *dir, |
|
struct btrfs_key *location, u8 type, u64 index) |
|
{ |
|
int ret = 0; |
|
int ret2 = 0; |
|
struct btrfs_root *root = dir->root; |
|
struct btrfs_path *path; |
|
struct btrfs_dir_item *dir_item; |
|
struct extent_buffer *leaf; |
|
unsigned long name_ptr; |
|
struct btrfs_key key; |
|
struct btrfs_disk_key disk_key; |
|
u32 data_size; |
|
|
|
key.objectid = btrfs_ino(dir); |
|
key.type = BTRFS_DIR_ITEM_KEY; |
|
key.offset = btrfs_name_hash(name, name_len); |
|
|
|
path = btrfs_alloc_path(); |
|
if (!path) |
|
return -ENOMEM; |
|
|
|
btrfs_cpu_key_to_disk(&disk_key, location); |
|
|
|
data_size = sizeof(*dir_item) + name_len; |
|
dir_item = insert_with_overflow(trans, root, path, &key, data_size, |
|
name, name_len); |
|
if (IS_ERR(dir_item)) { |
|
ret = PTR_ERR(dir_item); |
|
if (ret == -EEXIST) |
|
goto second_insert; |
|
goto out_free; |
|
} |
|
|
|
leaf = path->nodes[0]; |
|
btrfs_set_dir_item_key(leaf, dir_item, &disk_key); |
|
btrfs_set_dir_type(leaf, dir_item, type); |
|
btrfs_set_dir_data_len(leaf, dir_item, 0); |
|
btrfs_set_dir_name_len(leaf, dir_item, name_len); |
|
btrfs_set_dir_transid(leaf, dir_item, trans->transid); |
|
name_ptr = (unsigned long)(dir_item + 1); |
|
|
|
write_extent_buffer(leaf, name, name_ptr, name_len); |
|
btrfs_mark_buffer_dirty(leaf); |
|
|
|
second_insert: |
|
/* FIXME, use some real flag for selecting the extra index */ |
|
if (root == root->fs_info->tree_root) { |
|
ret = 0; |
|
goto out_free; |
|
} |
|
btrfs_release_path(path); |
|
|
|
ret2 = btrfs_insert_delayed_dir_index(trans, name, name_len, dir, |
|
&disk_key, type, index); |
|
out_free: |
|
btrfs_free_path(path); |
|
if (ret) |
|
return ret; |
|
if (ret2) |
|
return ret2; |
|
return 0; |
|
} |
|
|
|
static struct btrfs_dir_item *btrfs_lookup_match_dir( |
|
struct btrfs_trans_handle *trans, |
|
struct btrfs_root *root, struct btrfs_path *path, |
|
struct btrfs_key *key, const char *name, |
|
int name_len, int mod) |
|
{ |
|
const int ins_len = (mod < 0 ? -1 : 0); |
|
const int cow = (mod != 0); |
|
int ret; |
|
|
|
ret = btrfs_search_slot(trans, root, key, path, ins_len, cow); |
|
if (ret < 0) |
|
return ERR_PTR(ret); |
|
if (ret > 0) |
|
return ERR_PTR(-ENOENT); |
|
|
|
return btrfs_match_dir_item_name(root->fs_info, path, name, name_len); |
|
} |
|
|
|
/* |
|
* Lookup for a directory item by name. |
|
* |
|
* @trans: The transaction handle to use. Can be NULL if @mod is 0. |
|
* @root: The root of the target tree. |
|
* @path: Path to use for the search. |
|
* @dir: The inode number (objectid) of the directory. |
|
* @name: The name associated to the directory entry we are looking for. |
|
* @name_len: The length of the name. |
|
* @mod: Used to indicate if the tree search is meant for a read only |
|
* lookup, for a modification lookup or for a deletion lookup, so |
|
* its value should be 0, 1 or -1, respectively. |
|
* |
|
* Returns: NULL if the dir item does not exists, an error pointer if an error |
|
* happened, or a pointer to a dir item if a dir item exists for the given name. |
|
*/ |
|
struct btrfs_dir_item *btrfs_lookup_dir_item(struct btrfs_trans_handle *trans, |
|
struct btrfs_root *root, |
|
struct btrfs_path *path, u64 dir, |
|
const char *name, int name_len, |
|
int mod) |
|
{ |
|
struct btrfs_key key; |
|
struct btrfs_dir_item *di; |
|
|
|
key.objectid = dir; |
|
key.type = BTRFS_DIR_ITEM_KEY; |
|
key.offset = btrfs_name_hash(name, name_len); |
|
|
|
di = btrfs_lookup_match_dir(trans, root, path, &key, name, name_len, mod); |
|
if (IS_ERR(di) && PTR_ERR(di) == -ENOENT) |
|
return NULL; |
|
|
|
return di; |
|
} |
|
|
|
int btrfs_check_dir_item_collision(struct btrfs_root *root, u64 dir, |
|
const char *name, int name_len) |
|
{ |
|
int ret; |
|
struct btrfs_key key; |
|
struct btrfs_dir_item *di; |
|
int data_size; |
|
struct extent_buffer *leaf; |
|
int slot; |
|
struct btrfs_path *path; |
|
|
|
path = btrfs_alloc_path(); |
|
if (!path) |
|
return -ENOMEM; |
|
|
|
key.objectid = dir; |
|
key.type = BTRFS_DIR_ITEM_KEY; |
|
key.offset = btrfs_name_hash(name, name_len); |
|
|
|
di = btrfs_lookup_match_dir(NULL, root, path, &key, name, name_len, 0); |
|
if (IS_ERR(di)) { |
|
ret = PTR_ERR(di); |
|
/* Nothing found, we're safe */ |
|
if (ret == -ENOENT) { |
|
ret = 0; |
|
goto out; |
|
} |
|
|
|
if (ret < 0) |
|
goto out; |
|
} |
|
|
|
/* we found an item, look for our name in the item */ |
|
if (di) { |
|
/* our exact name was found */ |
|
ret = -EEXIST; |
|
goto out; |
|
} |
|
|
|
/* |
|
* see if there is room in the item to insert this |
|
* name |
|
*/ |
|
data_size = sizeof(*di) + name_len; |
|
leaf = path->nodes[0]; |
|
slot = path->slots[0]; |
|
if (data_size + btrfs_item_size(leaf, slot) + |
|
sizeof(struct btrfs_item) > BTRFS_LEAF_DATA_SIZE(root->fs_info)) { |
|
ret = -EOVERFLOW; |
|
} else { |
|
/* plenty of insertion room */ |
|
ret = 0; |
|
} |
|
out: |
|
btrfs_free_path(path); |
|
return ret; |
|
} |
|
|
|
/* |
|
* Lookup for a directory index item by name and index number. |
|
* |
|
* @trans: The transaction handle to use. Can be NULL if @mod is 0. |
|
* @root: The root of the target tree. |
|
* @path: Path to use for the search. |
|
* @dir: The inode number (objectid) of the directory. |
|
* @index: The index number. |
|
* @name: The name associated to the directory entry we are looking for. |
|
* @name_len: The length of the name. |
|
* @mod: Used to indicate if the tree search is meant for a read only |
|
* lookup, for a modification lookup or for a deletion lookup, so |
|
* its value should be 0, 1 or -1, respectively. |
|
* |
|
* Returns: NULL if the dir index item does not exists, an error pointer if an |
|
* error happened, or a pointer to a dir item if the dir index item exists and |
|
* matches the criteria (name and index number). |
|
*/ |
|
struct btrfs_dir_item * |
|
btrfs_lookup_dir_index_item(struct btrfs_trans_handle *trans, |
|
struct btrfs_root *root, |
|
struct btrfs_path *path, u64 dir, |
|
u64 index, const char *name, int name_len, |
|
int mod) |
|
{ |
|
struct btrfs_dir_item *di; |
|
struct btrfs_key key; |
|
|
|
key.objectid = dir; |
|
key.type = BTRFS_DIR_INDEX_KEY; |
|
key.offset = index; |
|
|
|
di = btrfs_lookup_match_dir(trans, root, path, &key, name, name_len, mod); |
|
if (di == ERR_PTR(-ENOENT)) |
|
return NULL; |
|
|
|
return di; |
|
} |
|
|
|
struct btrfs_dir_item * |
|
btrfs_search_dir_index_item(struct btrfs_root *root, |
|
struct btrfs_path *path, u64 dirid, |
|
const char *name, int name_len) |
|
{ |
|
struct btrfs_dir_item *di; |
|
struct btrfs_key key; |
|
int ret; |
|
|
|
key.objectid = dirid; |
|
key.type = BTRFS_DIR_INDEX_KEY; |
|
key.offset = 0; |
|
|
|
btrfs_for_each_slot(root, &key, &key, path, ret) { |
|
if (key.objectid != dirid || key.type != BTRFS_DIR_INDEX_KEY) |
|
break; |
|
|
|
di = btrfs_match_dir_item_name(root->fs_info, path, |
|
name, name_len); |
|
if (di) |
|
return di; |
|
} |
|
/* Adjust return code if the key was not found in the next leaf. */ |
|
if (ret > 0) |
|
ret = 0; |
|
|
|
return ERR_PTR(ret); |
|
} |
|
|
|
struct btrfs_dir_item *btrfs_lookup_xattr(struct btrfs_trans_handle *trans, |
|
struct btrfs_root *root, |
|
struct btrfs_path *path, u64 dir, |
|
const char *name, u16 name_len, |
|
int mod) |
|
{ |
|
struct btrfs_key key; |
|
struct btrfs_dir_item *di; |
|
|
|
key.objectid = dir; |
|
key.type = BTRFS_XATTR_ITEM_KEY; |
|
key.offset = btrfs_name_hash(name, name_len); |
|
|
|
di = btrfs_lookup_match_dir(trans, root, path, &key, name, name_len, mod); |
|
if (IS_ERR(di) && PTR_ERR(di) == -ENOENT) |
|
return NULL; |
|
|
|
return di; |
|
} |
|
|
|
/* |
|
* helper function to look at the directory item pointed to by 'path' |
|
* this walks through all the entries in a dir item and finds one |
|
* for a specific name. |
|
*/ |
|
struct btrfs_dir_item *btrfs_match_dir_item_name(struct btrfs_fs_info *fs_info, |
|
struct btrfs_path *path, |
|
const char *name, int name_len) |
|
{ |
|
struct btrfs_dir_item *dir_item; |
|
unsigned long name_ptr; |
|
u32 total_len; |
|
u32 cur = 0; |
|
u32 this_len; |
|
struct extent_buffer *leaf; |
|
|
|
leaf = path->nodes[0]; |
|
dir_item = btrfs_item_ptr(leaf, path->slots[0], struct btrfs_dir_item); |
|
|
|
total_len = btrfs_item_size(leaf, path->slots[0]); |
|
while (cur < total_len) { |
|
this_len = sizeof(*dir_item) + |
|
btrfs_dir_name_len(leaf, dir_item) + |
|
btrfs_dir_data_len(leaf, dir_item); |
|
name_ptr = (unsigned long)(dir_item + 1); |
|
|
|
if (btrfs_dir_name_len(leaf, dir_item) == name_len && |
|
memcmp_extent_buffer(leaf, name, name_ptr, name_len) == 0) |
|
return dir_item; |
|
|
|
cur += this_len; |
|
dir_item = (struct btrfs_dir_item *)((char *)dir_item + |
|
this_len); |
|
} |
|
return NULL; |
|
} |
|
|
|
/* |
|
* given a pointer into a directory item, delete it. This |
|
* handles items that have more than one entry in them. |
|
*/ |
|
int btrfs_delete_one_dir_name(struct btrfs_trans_handle *trans, |
|
struct btrfs_root *root, |
|
struct btrfs_path *path, |
|
struct btrfs_dir_item *di) |
|
{ |
|
|
|
struct extent_buffer *leaf; |
|
u32 sub_item_len; |
|
u32 item_len; |
|
int ret = 0; |
|
|
|
leaf = path->nodes[0]; |
|
sub_item_len = sizeof(*di) + btrfs_dir_name_len(leaf, di) + |
|
btrfs_dir_data_len(leaf, di); |
|
item_len = btrfs_item_size(leaf, path->slots[0]); |
|
if (sub_item_len == item_len) { |
|
ret = btrfs_del_item(trans, root, path); |
|
} else { |
|
/* MARKER */ |
|
unsigned long ptr = (unsigned long)di; |
|
unsigned long start; |
|
|
|
start = btrfs_item_ptr_offset(leaf, path->slots[0]); |
|
memmove_extent_buffer(leaf, ptr, ptr + sub_item_len, |
|
item_len - (ptr + sub_item_len - start)); |
|
btrfs_truncate_item(path, item_len - sub_item_len, 1); |
|
} |
|
return ret; |
|
}
|
|
|