1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Copyright (C) STRATO AG 2013. All rights reserved. 4 */ 5 6 #include <linux/uuid.h> 7 #include <asm/unaligned.h> 8 #include "ctree.h" 9 #include "transaction.h" 10 #include "disk-io.h" 11 #include "print-tree.h" 12 13 14 static void btrfs_uuid_to_key(u8 *uuid, u8 type, struct btrfs_key *key) 15 { 16 key->type = type; 17 key->objectid = get_unaligned_le64(uuid); 18 key->offset = get_unaligned_le64(uuid + sizeof(u64)); 19 } 20 21 /* return -ENOENT for !found, < 0 for errors, or 0 if an item was found */ 22 static int btrfs_uuid_tree_lookup(struct btrfs_root *uuid_root, u8 *uuid, 23 u8 type, u64 subid) 24 { 25 int ret; 26 struct btrfs_path *path = NULL; 27 struct extent_buffer *eb; 28 int slot; 29 u32 item_size; 30 unsigned long offset; 31 struct btrfs_key key; 32 33 if (WARN_ON_ONCE(!uuid_root)) { 34 ret = -ENOENT; 35 goto out; 36 } 37 38 path = btrfs_alloc_path(); 39 if (!path) { 40 ret = -ENOMEM; 41 goto out; 42 } 43 44 btrfs_uuid_to_key(uuid, type, &key); 45 ret = btrfs_search_slot(NULL, uuid_root, &key, path, 0, 0); 46 if (ret < 0) { 47 goto out; 48 } else if (ret > 0) { 49 ret = -ENOENT; 50 goto out; 51 } 52 53 eb = path->nodes[0]; 54 slot = path->slots[0]; 55 item_size = btrfs_item_size_nr(eb, slot); 56 offset = btrfs_item_ptr_offset(eb, slot); 57 ret = -ENOENT; 58 59 if (!IS_ALIGNED(item_size, sizeof(u64))) { 60 btrfs_warn(uuid_root->fs_info, 61 "uuid item with illegal size %lu!", 62 (unsigned long)item_size); 63 goto out; 64 } 65 while (item_size) { 66 __le64 data; 67 68 read_extent_buffer(eb, &data, offset, sizeof(data)); 69 if (le64_to_cpu(data) == subid) { 70 ret = 0; 71 break; 72 } 73 offset += sizeof(data); 74 item_size -= sizeof(data); 75 } 76 77 out: 78 btrfs_free_path(path); 79 return ret; 80 } 81 82 int btrfs_uuid_tree_add(struct btrfs_trans_handle *trans, u8 *uuid, u8 type, 83 u64 subid_cpu) 84 { 85 struct btrfs_fs_info *fs_info = trans->fs_info; 86 struct btrfs_root *uuid_root = fs_info->uuid_root; 87 int ret; 88 struct btrfs_path *path = NULL; 89 struct btrfs_key key; 90 struct extent_buffer *eb; 91 int slot; 92 unsigned long offset; 93 __le64 subid_le; 94 95 ret = btrfs_uuid_tree_lookup(uuid_root, uuid, type, subid_cpu); 96 if (ret != -ENOENT) 97 return ret; 98 99 if (WARN_ON_ONCE(!uuid_root)) { 100 ret = -EINVAL; 101 goto out; 102 } 103 104 btrfs_uuid_to_key(uuid, type, &key); 105 106 path = btrfs_alloc_path(); 107 if (!path) { 108 ret = -ENOMEM; 109 goto out; 110 } 111 112 ret = btrfs_insert_empty_item(trans, uuid_root, path, &key, 113 sizeof(subid_le)); 114 if (ret >= 0) { 115 /* Add an item for the type for the first time */ 116 eb = path->nodes[0]; 117 slot = path->slots[0]; 118 offset = btrfs_item_ptr_offset(eb, slot); 119 } else if (ret == -EEXIST) { 120 /* 121 * An item with that type already exists. 122 * Extend the item and store the new subid at the end. 123 */ 124 btrfs_extend_item(path, sizeof(subid_le)); 125 eb = path->nodes[0]; 126 slot = path->slots[0]; 127 offset = btrfs_item_ptr_offset(eb, slot); 128 offset += btrfs_item_size_nr(eb, slot) - sizeof(subid_le); 129 } else { 130 btrfs_warn(fs_info, 131 "insert uuid item failed %d (0x%016llx, 0x%016llx) type %u!", 132 ret, (unsigned long long)key.objectid, 133 (unsigned long long)key.offset, type); 134 goto out; 135 } 136 137 ret = 0; 138 subid_le = cpu_to_le64(subid_cpu); 139 write_extent_buffer(eb, &subid_le, offset, sizeof(subid_le)); 140 btrfs_mark_buffer_dirty(eb); 141 142 out: 143 btrfs_free_path(path); 144 return ret; 145 } 146 147 int btrfs_uuid_tree_remove(struct btrfs_trans_handle *trans, u8 *uuid, u8 type, 148 u64 subid) 149 { 150 struct btrfs_fs_info *fs_info = trans->fs_info; 151 struct btrfs_root *uuid_root = fs_info->uuid_root; 152 int ret; 153 struct btrfs_path *path = NULL; 154 struct btrfs_key key; 155 struct extent_buffer *eb; 156 int slot; 157 unsigned long offset; 158 u32 item_size; 159 unsigned long move_dst; 160 unsigned long move_src; 161 unsigned long move_len; 162 163 if (WARN_ON_ONCE(!uuid_root)) { 164 ret = -EINVAL; 165 goto out; 166 } 167 168 btrfs_uuid_to_key(uuid, type, &key); 169 170 path = btrfs_alloc_path(); 171 if (!path) { 172 ret = -ENOMEM; 173 goto out; 174 } 175 176 ret = btrfs_search_slot(trans, uuid_root, &key, path, -1, 1); 177 if (ret < 0) { 178 btrfs_warn(fs_info, "error %d while searching for uuid item!", 179 ret); 180 goto out; 181 } 182 if (ret > 0) { 183 ret = -ENOENT; 184 goto out; 185 } 186 187 eb = path->nodes[0]; 188 slot = path->slots[0]; 189 offset = btrfs_item_ptr_offset(eb, slot); 190 item_size = btrfs_item_size_nr(eb, slot); 191 if (!IS_ALIGNED(item_size, sizeof(u64))) { 192 btrfs_warn(fs_info, "uuid item with illegal size %lu!", 193 (unsigned long)item_size); 194 ret = -ENOENT; 195 goto out; 196 } 197 while (item_size) { 198 __le64 read_subid; 199 200 read_extent_buffer(eb, &read_subid, offset, sizeof(read_subid)); 201 if (le64_to_cpu(read_subid) == subid) 202 break; 203 offset += sizeof(read_subid); 204 item_size -= sizeof(read_subid); 205 } 206 207 if (!item_size) { 208 ret = -ENOENT; 209 goto out; 210 } 211 212 item_size = btrfs_item_size_nr(eb, slot); 213 if (item_size == sizeof(subid)) { 214 ret = btrfs_del_item(trans, uuid_root, path); 215 goto out; 216 } 217 218 move_dst = offset; 219 move_src = offset + sizeof(subid); 220 move_len = item_size - (move_src - btrfs_item_ptr_offset(eb, slot)); 221 memmove_extent_buffer(eb, move_dst, move_src, move_len); 222 btrfs_truncate_item(path, item_size - sizeof(subid), 1); 223 224 out: 225 btrfs_free_path(path); 226 return ret; 227 } 228 229 static int btrfs_uuid_iter_rem(struct btrfs_root *uuid_root, u8 *uuid, u8 type, 230 u64 subid) 231 { 232 struct btrfs_trans_handle *trans; 233 int ret; 234 235 /* 1 - for the uuid item */ 236 trans = btrfs_start_transaction(uuid_root, 1); 237 if (IS_ERR(trans)) { 238 ret = PTR_ERR(trans); 239 goto out; 240 } 241 242 ret = btrfs_uuid_tree_remove(trans, uuid, type, subid); 243 btrfs_end_transaction(trans); 244 245 out: 246 return ret; 247 } 248 249 int btrfs_uuid_tree_iterate(struct btrfs_fs_info *fs_info, 250 int (*check_func)(struct btrfs_fs_info *, u8 *, u8, 251 u64)) 252 { 253 struct btrfs_root *root = fs_info->uuid_root; 254 struct btrfs_key key; 255 struct btrfs_path *path; 256 int ret = 0; 257 struct extent_buffer *leaf; 258 int slot; 259 u32 item_size; 260 unsigned long offset; 261 262 path = btrfs_alloc_path(); 263 if (!path) { 264 ret = -ENOMEM; 265 goto out; 266 } 267 268 key.objectid = 0; 269 key.type = 0; 270 key.offset = 0; 271 272 again_search_slot: 273 ret = btrfs_search_forward(root, &key, path, BTRFS_OLDEST_GENERATION); 274 if (ret) { 275 if (ret > 0) 276 ret = 0; 277 goto out; 278 } 279 280 while (1) { 281 cond_resched(); 282 leaf = path->nodes[0]; 283 slot = path->slots[0]; 284 btrfs_item_key_to_cpu(leaf, &key, slot); 285 286 if (key.type != BTRFS_UUID_KEY_SUBVOL && 287 key.type != BTRFS_UUID_KEY_RECEIVED_SUBVOL) 288 goto skip; 289 290 offset = btrfs_item_ptr_offset(leaf, slot); 291 item_size = btrfs_item_size_nr(leaf, slot); 292 if (!IS_ALIGNED(item_size, sizeof(u64))) { 293 btrfs_warn(fs_info, 294 "uuid item with illegal size %lu!", 295 (unsigned long)item_size); 296 goto skip; 297 } 298 while (item_size) { 299 u8 uuid[BTRFS_UUID_SIZE]; 300 __le64 subid_le; 301 u64 subid_cpu; 302 303 put_unaligned_le64(key.objectid, uuid); 304 put_unaligned_le64(key.offset, uuid + sizeof(u64)); 305 read_extent_buffer(leaf, &subid_le, offset, 306 sizeof(subid_le)); 307 subid_cpu = le64_to_cpu(subid_le); 308 ret = check_func(fs_info, uuid, key.type, subid_cpu); 309 if (ret < 0) 310 goto out; 311 if (ret > 0) { 312 btrfs_release_path(path); 313 ret = btrfs_uuid_iter_rem(root, uuid, key.type, 314 subid_cpu); 315 if (ret == 0) { 316 /* 317 * this might look inefficient, but the 318 * justification is that it is an 319 * exception that check_func returns 1, 320 * and that in the regular case only one 321 * entry per UUID exists. 322 */ 323 goto again_search_slot; 324 } 325 if (ret < 0 && ret != -ENOENT) 326 goto out; 327 } 328 item_size -= sizeof(subid_le); 329 offset += sizeof(subid_le); 330 } 331 332 skip: 333 ret = btrfs_next_item(root, path); 334 if (ret == 0) 335 continue; 336 else if (ret > 0) 337 ret = 0; 338 break; 339 } 340 341 out: 342 btrfs_free_path(path); 343 return ret; 344 } 345