1 /* 2 * QEMU Block driver for native access to files on NFS shares 3 * 4 * Copyright (c) 2014 Peter Lieven <pl@kamp.de> 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a copy 7 * of this software and associated documentation files (the "Software"), to deal 8 * in the Software without restriction, including without limitation the rights 9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 * copies of the Software, and to permit persons to whom the Software is 11 * furnished to do so, subject to the following conditions: 12 * 13 * The above copyright notice and this permission notice shall be included in 14 * all copies or substantial portions of the Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 * THE SOFTWARE. 23 */ 24 25 #include "config-host.h" 26 27 #include <poll.h> 28 #include "qemu-common.h" 29 #include "qemu/config-file.h" 30 #include "qemu/error-report.h" 31 #include "block/block_int.h" 32 #include "trace.h" 33 #include "qemu/iov.h" 34 #include "qemu/uri.h" 35 #include "sysemu/sysemu.h" 36 #include <nfsc/libnfs.h> 37 38 #define QEMU_NFS_MAX_READAHEAD_SIZE 1048576 39 40 typedef struct NFSClient { 41 struct nfs_context *context; 42 struct nfsfh *fh; 43 int events; 44 bool has_zero_init; 45 AioContext *aio_context; 46 } NFSClient; 47 48 typedef struct NFSRPC { 49 int ret; 50 int complete; 51 QEMUIOVector *iov; 52 struct stat *st; 53 Coroutine *co; 54 QEMUBH *bh; 55 NFSClient *client; 56 } NFSRPC; 57 58 static void nfs_process_read(void *arg); 59 static void nfs_process_write(void *arg); 60 61 static void nfs_set_events(NFSClient *client) 62 { 63 int ev = nfs_which_events(client->context); 64 if (ev != client->events) { 65 aio_set_fd_handler(client->aio_context, 66 nfs_get_fd(client->context), 67 (ev & POLLIN) ? nfs_process_read : NULL, 68 (ev & POLLOUT) ? nfs_process_write : NULL, 69 client); 70 71 } 72 client->events = ev; 73 } 74 75 static void nfs_process_read(void *arg) 76 { 77 NFSClient *client = arg; 78 nfs_service(client->context, POLLIN); 79 nfs_set_events(client); 80 } 81 82 static void nfs_process_write(void *arg) 83 { 84 NFSClient *client = arg; 85 nfs_service(client->context, POLLOUT); 86 nfs_set_events(client); 87 } 88 89 static void nfs_co_init_task(NFSClient *client, NFSRPC *task) 90 { 91 *task = (NFSRPC) { 92 .co = qemu_coroutine_self(), 93 .client = client, 94 }; 95 } 96 97 static void nfs_co_generic_bh_cb(void *opaque) 98 { 99 NFSRPC *task = opaque; 100 task->complete = 1; 101 qemu_bh_delete(task->bh); 102 qemu_coroutine_enter(task->co, NULL); 103 } 104 105 static void 106 nfs_co_generic_cb(int ret, struct nfs_context *nfs, void *data, 107 void *private_data) 108 { 109 NFSRPC *task = private_data; 110 task->ret = ret; 111 if (task->ret > 0 && task->iov) { 112 if (task->ret <= task->iov->size) { 113 qemu_iovec_from_buf(task->iov, 0, data, task->ret); 114 } else { 115 task->ret = -EIO; 116 } 117 } 118 if (task->ret == 0 && task->st) { 119 memcpy(task->st, data, sizeof(struct stat)); 120 } 121 if (task->ret < 0) { 122 error_report("NFS Error: %s", nfs_get_error(nfs)); 123 } 124 if (task->co) { 125 task->bh = aio_bh_new(task->client->aio_context, 126 nfs_co_generic_bh_cb, task); 127 qemu_bh_schedule(task->bh); 128 } else { 129 task->complete = 1; 130 } 131 } 132 133 static int coroutine_fn nfs_co_readv(BlockDriverState *bs, 134 int64_t sector_num, int nb_sectors, 135 QEMUIOVector *iov) 136 { 137 NFSClient *client = bs->opaque; 138 NFSRPC task; 139 140 nfs_co_init_task(client, &task); 141 task.iov = iov; 142 143 if (nfs_pread_async(client->context, client->fh, 144 sector_num * BDRV_SECTOR_SIZE, 145 nb_sectors * BDRV_SECTOR_SIZE, 146 nfs_co_generic_cb, &task) != 0) { 147 return -ENOMEM; 148 } 149 150 while (!task.complete) { 151 nfs_set_events(client); 152 qemu_coroutine_yield(); 153 } 154 155 if (task.ret < 0) { 156 return task.ret; 157 } 158 159 /* zero pad short reads */ 160 if (task.ret < iov->size) { 161 qemu_iovec_memset(iov, task.ret, 0, iov->size - task.ret); 162 } 163 164 return 0; 165 } 166 167 static int coroutine_fn nfs_co_writev(BlockDriverState *bs, 168 int64_t sector_num, int nb_sectors, 169 QEMUIOVector *iov) 170 { 171 NFSClient *client = bs->opaque; 172 NFSRPC task; 173 char *buf = NULL; 174 175 nfs_co_init_task(client, &task); 176 177 buf = g_try_malloc(nb_sectors * BDRV_SECTOR_SIZE); 178 if (nb_sectors && buf == NULL) { 179 return -ENOMEM; 180 } 181 182 qemu_iovec_to_buf(iov, 0, buf, nb_sectors * BDRV_SECTOR_SIZE); 183 184 if (nfs_pwrite_async(client->context, client->fh, 185 sector_num * BDRV_SECTOR_SIZE, 186 nb_sectors * BDRV_SECTOR_SIZE, 187 buf, nfs_co_generic_cb, &task) != 0) { 188 g_free(buf); 189 return -ENOMEM; 190 } 191 192 while (!task.complete) { 193 nfs_set_events(client); 194 qemu_coroutine_yield(); 195 } 196 197 g_free(buf); 198 199 if (task.ret != nb_sectors * BDRV_SECTOR_SIZE) { 200 return task.ret < 0 ? task.ret : -EIO; 201 } 202 203 return 0; 204 } 205 206 static int coroutine_fn nfs_co_flush(BlockDriverState *bs) 207 { 208 NFSClient *client = bs->opaque; 209 NFSRPC task; 210 211 nfs_co_init_task(client, &task); 212 213 if (nfs_fsync_async(client->context, client->fh, nfs_co_generic_cb, 214 &task) != 0) { 215 return -ENOMEM; 216 } 217 218 while (!task.complete) { 219 nfs_set_events(client); 220 qemu_coroutine_yield(); 221 } 222 223 return task.ret; 224 } 225 226 /* TODO Convert to fine grained options */ 227 static QemuOptsList runtime_opts = { 228 .name = "nfs", 229 .head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head), 230 .desc = { 231 { 232 .name = "filename", 233 .type = QEMU_OPT_STRING, 234 .help = "URL to the NFS file", 235 }, 236 { /* end of list */ } 237 }, 238 }; 239 240 static void nfs_detach_aio_context(BlockDriverState *bs) 241 { 242 NFSClient *client = bs->opaque; 243 244 aio_set_fd_handler(client->aio_context, 245 nfs_get_fd(client->context), 246 NULL, NULL, NULL); 247 client->events = 0; 248 } 249 250 static void nfs_attach_aio_context(BlockDriverState *bs, 251 AioContext *new_context) 252 { 253 NFSClient *client = bs->opaque; 254 255 client->aio_context = new_context; 256 nfs_set_events(client); 257 } 258 259 static void nfs_client_close(NFSClient *client) 260 { 261 if (client->context) { 262 if (client->fh) { 263 nfs_close(client->context, client->fh); 264 } 265 aio_set_fd_handler(client->aio_context, 266 nfs_get_fd(client->context), 267 NULL, NULL, NULL); 268 nfs_destroy_context(client->context); 269 } 270 memset(client, 0, sizeof(NFSClient)); 271 } 272 273 static void nfs_file_close(BlockDriverState *bs) 274 { 275 NFSClient *client = bs->opaque; 276 nfs_client_close(client); 277 } 278 279 static int64_t nfs_client_open(NFSClient *client, const char *filename, 280 int flags, Error **errp) 281 { 282 int ret = -EINVAL, i; 283 struct stat st; 284 URI *uri; 285 QueryParams *qp = NULL; 286 char *file = NULL, *strp = NULL; 287 288 uri = uri_parse(filename); 289 if (!uri) { 290 error_setg(errp, "Invalid URL specified"); 291 goto fail; 292 } 293 if (!uri->server) { 294 error_setg(errp, "Invalid URL specified"); 295 goto fail; 296 } 297 strp = strrchr(uri->path, '/'); 298 if (strp == NULL) { 299 error_setg(errp, "Invalid URL specified"); 300 goto fail; 301 } 302 file = g_strdup(strp); 303 *strp = 0; 304 305 client->context = nfs_init_context(); 306 if (client->context == NULL) { 307 error_setg(errp, "Failed to init NFS context"); 308 goto fail; 309 } 310 311 qp = query_params_parse(uri->query); 312 for (i = 0; i < qp->n; i++) { 313 unsigned long long val; 314 if (!qp->p[i].value) { 315 error_setg(errp, "Value for NFS parameter expected: %s", 316 qp->p[i].name); 317 goto fail; 318 } 319 if (parse_uint_full(qp->p[i].value, &val, 0)) { 320 error_setg(errp, "Illegal value for NFS parameter: %s", 321 qp->p[i].name); 322 goto fail; 323 } 324 if (!strcmp(qp->p[i].name, "uid")) { 325 nfs_set_uid(client->context, val); 326 } else if (!strcmp(qp->p[i].name, "gid")) { 327 nfs_set_gid(client->context, val); 328 } else if (!strcmp(qp->p[i].name, "tcp-syncnt")) { 329 nfs_set_tcp_syncnt(client->context, val); 330 #ifdef LIBNFS_FEATURE_READAHEAD 331 } else if (!strcmp(qp->p[i].name, "readahead")) { 332 if (val > QEMU_NFS_MAX_READAHEAD_SIZE) { 333 error_report("NFS Warning: Truncating NFS readahead" 334 " size to %d", QEMU_NFS_MAX_READAHEAD_SIZE); 335 val = QEMU_NFS_MAX_READAHEAD_SIZE; 336 } 337 nfs_set_readahead(client->context, val); 338 #endif 339 } else { 340 error_setg(errp, "Unknown NFS parameter name: %s", 341 qp->p[i].name); 342 goto fail; 343 } 344 } 345 346 ret = nfs_mount(client->context, uri->server, uri->path); 347 if (ret < 0) { 348 error_setg(errp, "Failed to mount nfs share: %s", 349 nfs_get_error(client->context)); 350 goto fail; 351 } 352 353 if (flags & O_CREAT) { 354 ret = nfs_creat(client->context, file, 0600, &client->fh); 355 if (ret < 0) { 356 error_setg(errp, "Failed to create file: %s", 357 nfs_get_error(client->context)); 358 goto fail; 359 } 360 } else { 361 ret = nfs_open(client->context, file, flags, &client->fh); 362 if (ret < 0) { 363 error_setg(errp, "Failed to open file : %s", 364 nfs_get_error(client->context)); 365 goto fail; 366 } 367 } 368 369 ret = nfs_fstat(client->context, client->fh, &st); 370 if (ret < 0) { 371 error_setg(errp, "Failed to fstat file: %s", 372 nfs_get_error(client->context)); 373 goto fail; 374 } 375 376 ret = DIV_ROUND_UP(st.st_size, BDRV_SECTOR_SIZE); 377 client->has_zero_init = S_ISREG(st.st_mode); 378 goto out; 379 fail: 380 nfs_client_close(client); 381 out: 382 if (qp) { 383 query_params_free(qp); 384 } 385 uri_free(uri); 386 g_free(file); 387 return ret; 388 } 389 390 static int nfs_file_open(BlockDriverState *bs, QDict *options, int flags, 391 Error **errp) { 392 NFSClient *client = bs->opaque; 393 int64_t ret; 394 QemuOpts *opts; 395 Error *local_err = NULL; 396 397 client->aio_context = bdrv_get_aio_context(bs); 398 399 opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort); 400 qemu_opts_absorb_qdict(opts, options, &local_err); 401 if (local_err) { 402 error_propagate(errp, local_err); 403 ret = -EINVAL; 404 goto out; 405 } 406 ret = nfs_client_open(client, qemu_opt_get(opts, "filename"), 407 (flags & BDRV_O_RDWR) ? O_RDWR : O_RDONLY, 408 errp); 409 if (ret < 0) { 410 goto out; 411 } 412 bs->total_sectors = ret; 413 ret = 0; 414 out: 415 qemu_opts_del(opts); 416 return ret; 417 } 418 419 static QemuOptsList nfs_create_opts = { 420 .name = "nfs-create-opts", 421 .head = QTAILQ_HEAD_INITIALIZER(nfs_create_opts.head), 422 .desc = { 423 { 424 .name = BLOCK_OPT_SIZE, 425 .type = QEMU_OPT_SIZE, 426 .help = "Virtual disk size" 427 }, 428 { /* end of list */ } 429 } 430 }; 431 432 static int nfs_file_create(const char *url, QemuOpts *opts, Error **errp) 433 { 434 int ret = 0; 435 int64_t total_size = 0; 436 NFSClient *client = g_new0(NFSClient, 1); 437 438 client->aio_context = qemu_get_aio_context(); 439 440 /* Read out options */ 441 total_size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0), 442 BDRV_SECTOR_SIZE); 443 444 ret = nfs_client_open(client, url, O_CREAT, errp); 445 if (ret < 0) { 446 goto out; 447 } 448 ret = nfs_ftruncate(client->context, client->fh, total_size); 449 nfs_client_close(client); 450 out: 451 g_free(client); 452 return ret; 453 } 454 455 static int nfs_has_zero_init(BlockDriverState *bs) 456 { 457 NFSClient *client = bs->opaque; 458 return client->has_zero_init; 459 } 460 461 static int64_t nfs_get_allocated_file_size(BlockDriverState *bs) 462 { 463 NFSClient *client = bs->opaque; 464 NFSRPC task = {0}; 465 struct stat st; 466 467 task.st = &st; 468 if (nfs_fstat_async(client->context, client->fh, nfs_co_generic_cb, 469 &task) != 0) { 470 return -ENOMEM; 471 } 472 473 while (!task.complete) { 474 nfs_set_events(client); 475 aio_poll(client->aio_context, true); 476 } 477 478 return (task.ret < 0 ? task.ret : st.st_blocks * st.st_blksize); 479 } 480 481 static int nfs_file_truncate(BlockDriverState *bs, int64_t offset) 482 { 483 NFSClient *client = bs->opaque; 484 return nfs_ftruncate(client->context, client->fh, offset); 485 } 486 487 static BlockDriver bdrv_nfs = { 488 .format_name = "nfs", 489 .protocol_name = "nfs", 490 491 .instance_size = sizeof(NFSClient), 492 .bdrv_needs_filename = true, 493 .create_opts = &nfs_create_opts, 494 495 .bdrv_has_zero_init = nfs_has_zero_init, 496 .bdrv_get_allocated_file_size = nfs_get_allocated_file_size, 497 .bdrv_truncate = nfs_file_truncate, 498 499 .bdrv_file_open = nfs_file_open, 500 .bdrv_close = nfs_file_close, 501 .bdrv_create = nfs_file_create, 502 503 .bdrv_co_readv = nfs_co_readv, 504 .bdrv_co_writev = nfs_co_writev, 505 .bdrv_co_flush_to_disk = nfs_co_flush, 506 507 .bdrv_detach_aio_context = nfs_detach_aio_context, 508 .bdrv_attach_aio_context = nfs_attach_aio_context, 509 }; 510 511 static void nfs_block_init(void) 512 { 513 bdrv_register(&bdrv_nfs); 514 } 515 516 block_init(nfs_block_init); 517