1 /* 2 * Block node graph modifications tests 3 * 4 * Copyright (c) 2019-2021 Virtuozzo International GmbH. All rights reserved. 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or 9 * (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program. If not, see <http://www.gnu.org/licenses/>. 18 * 19 */ 20 21 #include "qemu/osdep.h" 22 #include "qapi/error.h" 23 #include "qemu/main-loop.h" 24 #include "block/block_int.h" 25 #include "sysemu/block-backend.h" 26 27 static BlockDriver bdrv_pass_through = { 28 .format_name = "pass-through", 29 .bdrv_child_perm = bdrv_default_perms, 30 }; 31 32 static void no_perm_default_perms(BlockDriverState *bs, BdrvChild *c, 33 BdrvChildRole role, 34 BlockReopenQueue *reopen_queue, 35 uint64_t perm, uint64_t shared, 36 uint64_t *nperm, uint64_t *nshared) 37 { 38 *nperm = 0; 39 *nshared = BLK_PERM_ALL; 40 } 41 42 static BlockDriver bdrv_no_perm = { 43 .format_name = "no-perm", 44 .supports_backing = true, 45 .bdrv_child_perm = no_perm_default_perms, 46 }; 47 48 static void exclusive_write_perms(BlockDriverState *bs, BdrvChild *c, 49 BdrvChildRole role, 50 BlockReopenQueue *reopen_queue, 51 uint64_t perm, uint64_t shared, 52 uint64_t *nperm, uint64_t *nshared) 53 { 54 *nperm = BLK_PERM_WRITE; 55 *nshared = BLK_PERM_ALL & ~BLK_PERM_WRITE; 56 } 57 58 static BlockDriver bdrv_exclusive_writer = { 59 .format_name = "exclusive-writer", 60 .bdrv_child_perm = exclusive_write_perms, 61 }; 62 63 static BlockDriverState *no_perm_node(const char *name) 64 { 65 return bdrv_new_open_driver(&bdrv_no_perm, name, BDRV_O_RDWR, &error_abort); 66 } 67 68 static BlockDriverState *pass_through_node(const char *name) 69 { 70 return bdrv_new_open_driver(&bdrv_pass_through, name, 71 BDRV_O_RDWR, &error_abort); 72 } 73 74 static BlockDriverState *exclusive_writer_node(const char *name) 75 { 76 return bdrv_new_open_driver(&bdrv_exclusive_writer, name, 77 BDRV_O_RDWR, &error_abort); 78 } 79 80 /* 81 * test_update_perm_tree 82 * 83 * When checking node for a possibility to update permissions, it's subtree 84 * should be correctly checked too. New permissions for each node should be 85 * calculated and checked in context of permissions of other nodes. If we 86 * check new permissions of the node only in context of old permissions of 87 * its neighbors, we can finish up with wrong permission graph. 88 * 89 * This test firstly create the following graph: 90 * +--------+ 91 * | root | 92 * +--------+ 93 * | 94 * | perm: write, read 95 * | shared: except write 96 * v 97 * +-------------------+ +----------------+ 98 * | passtrough filter |---------->| null-co node | 99 * +-------------------+ +----------------+ 100 * 101 * 102 * and then, tries to append filter under node. Expected behavior: fail. 103 * Otherwise we'll get the following picture, with two BdrvChild'ren, having 104 * write permission to one node, without actually sharing it. 105 * 106 * +--------+ 107 * | root | 108 * +--------+ 109 * | 110 * | perm: write, read 111 * | shared: except write 112 * v 113 * +-------------------+ 114 * | passtrough filter | 115 * +-------------------+ 116 * | | 117 * perm: write, read | | perm: write, read 118 * shared: except write | | shared: except write 119 * v v 120 * +----------------+ 121 * | null co node | 122 * +----------------+ 123 */ 124 static void test_update_perm_tree(void) 125 { 126 int ret; 127 128 BlockBackend *root = blk_new(qemu_get_aio_context(), 129 BLK_PERM_WRITE | BLK_PERM_CONSISTENT_READ, 130 BLK_PERM_ALL & ~BLK_PERM_WRITE); 131 BlockDriverState *bs = no_perm_node("node"); 132 BlockDriverState *filter = pass_through_node("filter"); 133 134 blk_insert_bs(root, bs, &error_abort); 135 136 bdrv_attach_child(filter, bs, "child", &child_of_bds, 137 BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY, &error_abort); 138 139 ret = bdrv_append(filter, bs, NULL); 140 g_assert_cmpint(ret, <, 0); 141 142 bdrv_unref(filter); 143 blk_unref(root); 144 } 145 146 /* 147 * test_should_update_child 148 * 149 * Test that bdrv_replace_node, and concretely should_update_child 150 * do the right thing, i.e. not creating loops on the graph. 151 * 152 * The test does the following: 153 * 1. initial graph: 154 * 155 * +------+ +--------+ 156 * | root | | filter | 157 * +------+ +--------+ 158 * | | 159 * root| target| 160 * v v 161 * +------+ +--------+ 162 * | node |<---------| target | 163 * +------+ backing +--------+ 164 * 165 * 2. Append @filter above @node. If should_update_child works correctly, 166 * it understands, that backing child of @target should not be updated, 167 * as it will create a loop on node graph. Resulting picture should 168 * be the left one, not the right: 169 * 170 * +------+ +------+ 171 * | root | | root | 172 * +------+ +------+ 173 * | | 174 * root| root| 175 * v v 176 * +--------+ target +--------+ target 177 * | filter |--------------+ | filter |--------------+ 178 * +--------+ | +--------+ | 179 * | | | ^ v 180 * backing| | backing| | +--------+ 181 * v v | +-----------| target | 182 * +------+ +--------+ v backing +--------+ 183 * | node |<---------| target | +------+ 184 * +------+ backing +--------+ | node | 185 * +------+ 186 * 187 * (good picture) (bad picture) 188 * 189 */ 190 static void test_should_update_child(void) 191 { 192 BlockBackend *root = blk_new(qemu_get_aio_context(), 0, BLK_PERM_ALL); 193 BlockDriverState *bs = no_perm_node("node"); 194 BlockDriverState *filter = no_perm_node("filter"); 195 BlockDriverState *target = no_perm_node("target"); 196 197 blk_insert_bs(root, bs, &error_abort); 198 199 bdrv_set_backing_hd(target, bs, &error_abort); 200 201 g_assert(target->backing->bs == bs); 202 bdrv_attach_child(filter, target, "target", &child_of_bds, 203 BDRV_CHILD_DATA, &error_abort); 204 bdrv_append(filter, bs, &error_abort); 205 g_assert(target->backing->bs == bs); 206 207 bdrv_unref(filter); 208 bdrv_unref(bs); 209 blk_unref(root); 210 } 211 212 /* 213 * test_parallel_exclusive_write 214 * 215 * Check that when we replace node, old permissions of the node being removed 216 * doesn't break the replacement. 217 */ 218 static void test_parallel_exclusive_write(void) 219 { 220 BlockDriverState *top = exclusive_writer_node("top"); 221 BlockDriverState *base = no_perm_node("base"); 222 BlockDriverState *fl1 = pass_through_node("fl1"); 223 BlockDriverState *fl2 = pass_through_node("fl2"); 224 225 /* 226 * bdrv_attach_child() eats child bs reference, so we need two @base 227 * references for two filters: 228 */ 229 bdrv_ref(base); 230 231 bdrv_attach_child(top, fl1, "backing", &child_of_bds, BDRV_CHILD_DATA, 232 &error_abort); 233 bdrv_attach_child(fl1, base, "backing", &child_of_bds, BDRV_CHILD_FILTERED, 234 &error_abort); 235 bdrv_attach_child(fl2, base, "backing", &child_of_bds, BDRV_CHILD_FILTERED, 236 &error_abort); 237 238 bdrv_replace_node(fl1, fl2, &error_abort); 239 240 bdrv_unref(fl2); 241 bdrv_unref(top); 242 } 243 244 /* 245 * write-to-selected node may have several DATA children, one of them may be 246 * "selected". Exclusive write permission is taken on selected child. 247 * 248 * We don't realize write handler itself, as we need only to test how permission 249 * update works. 250 */ 251 typedef struct BDRVWriteToSelectedState { 252 BdrvChild *selected; 253 } BDRVWriteToSelectedState; 254 255 static void write_to_selected_perms(BlockDriverState *bs, BdrvChild *c, 256 BdrvChildRole role, 257 BlockReopenQueue *reopen_queue, 258 uint64_t perm, uint64_t shared, 259 uint64_t *nperm, uint64_t *nshared) 260 { 261 BDRVWriteToSelectedState *s = bs->opaque; 262 263 if (s->selected && c == s->selected) { 264 *nperm = BLK_PERM_WRITE; 265 *nshared = BLK_PERM_ALL & ~BLK_PERM_WRITE; 266 } else { 267 *nperm = 0; 268 *nshared = BLK_PERM_ALL; 269 } 270 } 271 272 static BlockDriver bdrv_write_to_selected = { 273 .format_name = "write-to-selected", 274 .instance_size = sizeof(BDRVWriteToSelectedState), 275 .bdrv_child_perm = write_to_selected_perms, 276 }; 277 278 279 /* 280 * The following test shows that topological-sort order is required for 281 * permission update, simple DFS is not enough. 282 * 283 * Consider the block driver (write-to-selected) which has two children: one is 284 * selected so we have exclusive write access to it and for the other one we 285 * don't need any specific permissions. 286 * 287 * And, these two children has a common base child, like this: 288 * (additional "top" on top is used in test just because the only public 289 * function to update permission should get a specific child to update. 290 * Making bdrv_refresh_perms() public just for this test isn't worth it) 291 * 292 * ┌─────┐ ┌───────────────────┐ ┌─────┐ 293 * │ fl2 │ ◀── │ write-to-selected │ ◀── │ top │ 294 * └─────┘ └───────────────────┘ └─────┘ 295 * │ │ 296 * │ │ w 297 * │ ▼ 298 * │ ┌──────┐ 299 * │ │ fl1 │ 300 * │ └──────┘ 301 * │ │ 302 * │ │ w 303 * │ ▼ 304 * │ ┌──────┐ 305 * └───────▶ │ base │ 306 * └──────┘ 307 * 308 * So, exclusive write is propagated. 309 * 310 * Assume, we want to select fl2 instead of fl1. 311 * So, we set some option for write-to-selected driver and do permission update. 312 * 313 * With simple DFS, if permission update goes first through 314 * write-to-selected -> fl1 -> base branch it will succeed: it firstly drop 315 * exclusive write permissions and than apply them for another BdrvChildren. 316 * But if permission update goes first through write-to-selected -> fl2 -> base 317 * branch it will fail, as when we try to update fl2->base child, old not yet 318 * updated fl1->base child will be in conflict. 319 * 320 * With topological-sort order we always update parents before children, so fl1 321 * and fl2 are both updated when we update base and there is no conflict. 322 */ 323 static void test_parallel_perm_update(void) 324 { 325 BlockDriverState *top = no_perm_node("top"); 326 BlockDriverState *ws = 327 bdrv_new_open_driver(&bdrv_write_to_selected, "ws", BDRV_O_RDWR, 328 &error_abort); 329 BDRVWriteToSelectedState *s = ws->opaque; 330 BlockDriverState *base = no_perm_node("base"); 331 BlockDriverState *fl1 = pass_through_node("fl1"); 332 BlockDriverState *fl2 = pass_through_node("fl2"); 333 BdrvChild *c_fl1, *c_fl2; 334 335 /* 336 * bdrv_attach_child() eats child bs reference, so we need two @base 337 * references for two filters: 338 */ 339 bdrv_ref(base); 340 341 bdrv_attach_child(top, ws, "file", &child_of_bds, BDRV_CHILD_DATA, 342 &error_abort); 343 c_fl1 = bdrv_attach_child(ws, fl1, "first", &child_of_bds, 344 BDRV_CHILD_DATA, &error_abort); 345 c_fl2 = bdrv_attach_child(ws, fl2, "second", &child_of_bds, 346 BDRV_CHILD_DATA, &error_abort); 347 bdrv_attach_child(fl1, base, "backing", &child_of_bds, BDRV_CHILD_FILTERED, 348 &error_abort); 349 bdrv_attach_child(fl2, base, "backing", &child_of_bds, BDRV_CHILD_FILTERED, 350 &error_abort); 351 352 /* Select fl1 as first child to be active */ 353 s->selected = c_fl1; 354 bdrv_child_refresh_perms(top, top->children.lh_first, &error_abort); 355 356 assert(c_fl1->perm & BLK_PERM_WRITE); 357 assert(!(c_fl2->perm & BLK_PERM_WRITE)); 358 359 /* Now, try to switch active child and update permissions */ 360 s->selected = c_fl2; 361 bdrv_child_refresh_perms(top, top->children.lh_first, &error_abort); 362 363 assert(c_fl2->perm & BLK_PERM_WRITE); 364 assert(!(c_fl1->perm & BLK_PERM_WRITE)); 365 366 /* Switch once more, to not care about real child order in the list */ 367 s->selected = c_fl1; 368 bdrv_child_refresh_perms(top, top->children.lh_first, &error_abort); 369 370 assert(c_fl1->perm & BLK_PERM_WRITE); 371 assert(!(c_fl2->perm & BLK_PERM_WRITE)); 372 373 bdrv_unref(top); 374 } 375 376 /* 377 * It's possible that filter required permissions allows to insert it to backing 378 * chain, like: 379 * 380 * 1. [top] -> [filter] -> [base] 381 * 382 * but doesn't allow to add it as a branch: 383 * 384 * 2. [filter] --\ 385 * v 386 * [top] -> [base] 387 * 388 * So, inserting such filter should do all graph modifications and only then 389 * update permissions. If we try to go through intermediate state [2] and update 390 * permissions on it we'll fail. 391 * 392 * Let's check that bdrv_append() can append such a filter. 393 */ 394 static void test_append_greedy_filter(void) 395 { 396 BlockDriverState *top = exclusive_writer_node("top"); 397 BlockDriverState *base = no_perm_node("base"); 398 BlockDriverState *fl = exclusive_writer_node("fl1"); 399 400 bdrv_attach_child(top, base, "backing", &child_of_bds, BDRV_CHILD_COW, 401 &error_abort); 402 403 bdrv_append(fl, base, &error_abort); 404 bdrv_unref(fl); 405 bdrv_unref(top); 406 } 407 408 int main(int argc, char *argv[]) 409 { 410 bdrv_init(); 411 qemu_init_main_loop(&error_abort); 412 413 g_test_init(&argc, &argv, NULL); 414 415 g_test_add_func("/bdrv-graph-mod/update-perm-tree", test_update_perm_tree); 416 g_test_add_func("/bdrv-graph-mod/should-update-child", 417 test_should_update_child); 418 g_test_add_func("/bdrv-graph-mod/parallel-perm-update", 419 test_parallel_perm_update); 420 g_test_add_func("/bdrv-graph-mod/parallel-exclusive-write", 421 test_parallel_exclusive_write); 422 g_test_add_func("/bdrv-graph-mod/append-greedy-filter", 423 test_append_greedy_filter); 424 425 return g_test_run(); 426 } 427