aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/block/nbd.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/block/nbd.c')
-rw-r--r--drivers/block/nbd.c64
1 files changed, 48 insertions, 16 deletions
diff --git a/drivers/block/nbd.c b/drivers/block/nbd.c
index 41e62c9407e2..b74293762be7 100644
--- a/drivers/block/nbd.c
+++ b/drivers/block/nbd.c
@@ -135,6 +135,8 @@ static struct dentry *nbd_dbg_dir;
#define NBD_MAGIC 0x68797548
+#define NBD_DEF_BLKSIZE 1024
+
static unsigned int nbds_max = 16;
static int max_part = 16;
static int part_shift;
@@ -983,25 +985,26 @@ static int nbd_add_socket(struct nbd_device *nbd, unsigned long arg,
test_bit(NBD_BOUND, &config->runtime_flags))) {
dev_err(disk_to_dev(nbd->disk),
"Device being setup by another task");
- sockfd_put(sock);
- return -EBUSY;
+ err = -EBUSY;
+ goto put_socket;
+ }
+
+ nsock = kzalloc(sizeof(*nsock), GFP_KERNEL);
+ if (!nsock) {
+ err = -ENOMEM;
+ goto put_socket;
}
socks = krealloc(config->socks, (config->num_connections + 1) *
sizeof(struct nbd_sock *), GFP_KERNEL);
if (!socks) {
- sockfd_put(sock);
- return -ENOMEM;
+ kfree(nsock);
+ err = -ENOMEM;
+ goto put_socket;
}
config->socks = socks;
- nsock = kzalloc(sizeof(struct nbd_sock), GFP_KERNEL);
- if (!nsock) {
- sockfd_put(sock);
- return -ENOMEM;
- }
-
nsock->fallback_index = -1;
nsock->dead = false;
mutex_init(&nsock->tx_lock);
@@ -1013,6 +1016,10 @@ static int nbd_add_socket(struct nbd_device *nbd, unsigned long arg,
atomic_inc(&config->live_connections);
return 0;
+
+put_socket:
+ sockfd_put(sock);
+ return err;
}
static int nbd_reconnect_socket(struct nbd_device *nbd, unsigned long arg)
@@ -1225,6 +1232,16 @@ static int nbd_start_device(struct nbd_device *nbd)
args = kzalloc(sizeof(*args), GFP_KERNEL);
if (!args) {
sock_shutdown(nbd);
+ /*
+ * If num_connections is m (2 < m),
+ * and NO.1 ~ NO.n(1 < n < m) kzallocs are successful.
+ * But NO.(n + 1) failed. We still have n recv threads.
+ * So, add flush_workqueue here to prevent recv threads
+ * dropping the last config_refs and trying to destroy
+ * the workqueue from inside the workqueue.
+ */
+ if (i)
+ flush_workqueue(nbd->recv_workq);
return -ENOMEM;
}
sk_set_memalloc(config->socks[i]->sock->sk);
@@ -1256,10 +1273,10 @@ static int nbd_start_device_ioctl(struct nbd_device *nbd, struct block_device *b
mutex_unlock(&nbd->config_lock);
ret = wait_event_interruptible(config->recv_wq,
atomic_read(&config->recv_threads) == 0);
- if (ret) {
+ if (ret)
sock_shutdown(nbd);
- flush_workqueue(nbd->recv_workq);
- }
+ flush_workqueue(nbd->recv_workq);
+
mutex_lock(&nbd->config_lock);
nbd_bdev_reset(bdev);
/* user requested, ignore socket errors */
@@ -1281,6 +1298,14 @@ static void nbd_clear_sock_ioctl(struct nbd_device *nbd,
nbd_config_put(nbd);
}
+static bool nbd_is_valid_blksize(unsigned long blksize)
+{
+ if (!blksize || !is_power_of_2(blksize) || blksize < 512 ||
+ blksize > PAGE_SIZE)
+ return false;
+ return true;
+}
+
/* Must be called with config_lock held */
static int __nbd_ioctl(struct block_device *bdev, struct nbd_device *nbd,
unsigned int cmd, unsigned long arg)
@@ -1296,8 +1321,9 @@ static int __nbd_ioctl(struct block_device *bdev, struct nbd_device *nbd,
case NBD_SET_SOCK:
return nbd_add_socket(nbd, arg, false);
case NBD_SET_BLKSIZE:
- if (!arg || !is_power_of_2(arg) || arg < 512 ||
- arg > PAGE_SIZE)
+ if (!arg)
+ arg = NBD_DEF_BLKSIZE;
+ if (!nbd_is_valid_blksize(arg))
return -EINVAL;
nbd_size_set(nbd, arg,
div_s64(config->bytesize, arg));
@@ -1377,7 +1403,7 @@ static struct nbd_config *nbd_alloc_config(void)
atomic_set(&config->recv_threads, 0);
init_waitqueue_head(&config->recv_wq);
init_waitqueue_head(&config->conn_wait);
- config->blksize = 1024;
+ config->blksize = NBD_DEF_BLKSIZE;
atomic_set(&config->live_connections, 0);
try_module_get(THIS_MODULE);
return config;
@@ -1813,6 +1839,12 @@ again:
if (info->attrs[NBD_ATTR_BLOCK_SIZE_BYTES]) {
u64 bsize =
nla_get_u64(info->attrs[NBD_ATTR_BLOCK_SIZE_BYTES]);
+ if (!bsize)
+ bsize = NBD_DEF_BLKSIZE;
+ if (!nbd_is_valid_blksize(bsize)) {
+ ret = -EINVAL;
+ goto out;
+ }
nbd_size_set(nbd, bsize, div64_u64(config->bytesize, bsize));
}
if (info->attrs[NBD_ATTR_TIMEOUT]) {