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