diff --git a/drivers/block/rbd.c b/drivers/block/rbd.c index 3f162e21619418f441679b03bd1b856bcde4d3e9..5c129c54279c7fef6950f09eae1d0c9b9fffb88a 100644 --- a/drivers/block/rbd.c +++ b/drivers/block/rbd.c @@ -289,6 +289,7 @@ struct rbd_device { struct rbd_spec *parent_spec; u64 parent_overlap; + struct rbd_device *parent; /* protects updating the header */ struct rw_semaphore header_rwsem; @@ -335,6 +336,7 @@ static ssize_t rbd_add(struct bus_type *bus, const char *buf, size_t count); static ssize_t rbd_remove(struct bus_type *bus, const char *buf, size_t count); +static int rbd_dev_probe(struct rbd_device *rbd_dev); static struct bus_attribute rbd_bus_attrs[] = { __ATTR(add, S_IWUSR, NULL, rbd_add), @@ -497,6 +499,13 @@ static struct rbd_client *rbd_client_create(struct ceph_options *ceph_opts) return ERR_PTR(ret); } +static struct rbd_client *__rbd_get_client(struct rbd_client *rbdc) +{ + kref_get(&rbdc->kref); + + return rbdc; +} + /* * Find a ceph client with specific addr and configuration. If * found, bump its reference count. @@ -512,7 +521,8 @@ static struct rbd_client *rbd_client_find(struct ceph_options *ceph_opts) spin_lock(&rbd_client_list_lock); list_for_each_entry(client_node, &rbd_client_list, node) { if (!ceph_compare_options(ceph_opts, client_node->client)) { - kref_get(&client_node->kref); + __rbd_get_client(client_node); + found = true; break; } @@ -2741,8 +2751,6 @@ static struct rbd_spec *rbd_spec_alloc(void) return NULL; kref_init(&spec->kref); - rbd_spec_put(rbd_spec_get(spec)); /* TEMPORARY */ - return spec; } @@ -3837,6 +3845,11 @@ static int rbd_dev_image_id(struct rbd_device *rbd_dev) void *response; void *p; + /* If we already have it we don't need to look it up */ + + if (rbd_dev->spec->image_id) + return 0; + /* * When probing a parent image, the image id is already * known (and the image name likely is not). There's no @@ -4014,6 +4027,9 @@ static int rbd_dev_v2_probe(struct rbd_device *rbd_dev) static int rbd_dev_probe_finish(struct rbd_device *rbd_dev) { + struct rbd_device *parent = NULL; + struct rbd_spec *parent_spec = NULL; + struct rbd_client *rbdc = NULL; int ret; /* no need to lock here, as rbd_dev is not registered yet */ @@ -4058,6 +4074,31 @@ static int rbd_dev_probe_finish(struct rbd_device *rbd_dev) * At this point cleanup in the event of an error is the job * of the sysfs code (initiated by rbd_bus_del_dev()). */ + /* Probe the parent if there is one */ + + if (rbd_dev->parent_spec) { + /* + * We need to pass a reference to the client and the + * parent spec when creating the parent rbd_dev. + * Images related by parent/child relationships + * always share both. + */ + parent_spec = rbd_spec_get(rbd_dev->parent_spec); + rbdc = __rbd_get_client(rbd_dev->rbd_client); + + parent = rbd_dev_create(rbdc, parent_spec); + if (!parent) { + ret = -ENOMEM; + goto err_out_spec; + } + rbdc = NULL; /* parent now owns reference */ + parent_spec = NULL; /* parent now owns reference */ + ret = rbd_dev_probe(parent); + if (ret < 0) + goto err_out_parent; + rbd_dev->parent = parent; + } + down_write(&rbd_dev->header_rwsem); ret = rbd_dev_snaps_register(rbd_dev); up_write(&rbd_dev->header_rwsem); @@ -4076,6 +4117,12 @@ static int rbd_dev_probe_finish(struct rbd_device *rbd_dev) (unsigned long long) rbd_dev->mapping.size); return ret; + +err_out_parent: + rbd_dev_destroy(parent); +err_out_spec: + rbd_spec_put(parent_spec); + rbd_put_client(rbdc); err_out_bus: /* this will also clean up rest of rbd_dev stuff */ @@ -4239,6 +4286,12 @@ static void rbd_dev_release(struct device *dev) module_put(THIS_MODULE); } +static void __rbd_remove(struct rbd_device *rbd_dev) +{ + rbd_remove_all_snaps(rbd_dev); + rbd_bus_del_dev(rbd_dev); +} + static ssize_t rbd_remove(struct bus_type *bus, const char *buf, size_t count) @@ -4274,8 +4327,26 @@ static ssize_t rbd_remove(struct bus_type *bus, if (ret < 0) goto done; - rbd_remove_all_snaps(rbd_dev); - rbd_bus_del_dev(rbd_dev); + while (rbd_dev->parent_spec) { + struct rbd_device *first = rbd_dev; + struct rbd_device *second = first->parent; + struct rbd_device *third; + + /* + * Follow to the parent with no grandparent and + * remove it. + */ + while (second && (third = second->parent)) { + first = second; + second = third; + } + __rbd_remove(second); + rbd_spec_put(first->parent_spec); + first->parent_spec = NULL; + first->parent_overlap = 0; + first->parent = NULL; + } + __rbd_remove(rbd_dev); done: mutex_unlock(&ctl_mutex);