xref: /openbmc/qemu/block/nfs.c (revision 91bfcdb0)
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     blkcnt_t st_blocks;
47 } NFSClient;
48 
49 typedef struct NFSRPC {
50     int ret;
51     int complete;
52     QEMUIOVector *iov;
53     struct stat *st;
54     Coroutine *co;
55     QEMUBH *bh;
56     NFSClient *client;
57 } NFSRPC;
58 
59 static void nfs_process_read(void *arg);
60 static void nfs_process_write(void *arg);
61 
62 static void nfs_set_events(NFSClient *client)
63 {
64     int ev = nfs_which_events(client->context);
65     if (ev != client->events) {
66         aio_set_fd_handler(client->aio_context,
67                            nfs_get_fd(client->context),
68                            (ev & POLLIN) ? nfs_process_read : NULL,
69                            (ev & POLLOUT) ? nfs_process_write : NULL,
70                            client);
71 
72     }
73     client->events = ev;
74 }
75 
76 static void nfs_process_read(void *arg)
77 {
78     NFSClient *client = arg;
79     nfs_service(client->context, POLLIN);
80     nfs_set_events(client);
81 }
82 
83 static void nfs_process_write(void *arg)
84 {
85     NFSClient *client = arg;
86     nfs_service(client->context, POLLOUT);
87     nfs_set_events(client);
88 }
89 
90 static void nfs_co_init_task(NFSClient *client, NFSRPC *task)
91 {
92     *task = (NFSRPC) {
93         .co             = qemu_coroutine_self(),
94         .client         = client,
95     };
96 }
97 
98 static void nfs_co_generic_bh_cb(void *opaque)
99 {
100     NFSRPC *task = opaque;
101     task->complete = 1;
102     qemu_bh_delete(task->bh);
103     qemu_coroutine_enter(task->co, NULL);
104 }
105 
106 static void
107 nfs_co_generic_cb(int ret, struct nfs_context *nfs, void *data,
108                   void *private_data)
109 {
110     NFSRPC *task = private_data;
111     task->ret = ret;
112     if (task->ret > 0 && task->iov) {
113         if (task->ret <= task->iov->size) {
114             qemu_iovec_from_buf(task->iov, 0, data, task->ret);
115         } else {
116             task->ret = -EIO;
117         }
118     }
119     if (task->ret == 0 && task->st) {
120         memcpy(task->st, data, sizeof(struct stat));
121     }
122     if (task->ret < 0) {
123         error_report("NFS Error: %s", nfs_get_error(nfs));
124     }
125     if (task->co) {
126         task->bh = aio_bh_new(task->client->aio_context,
127                               nfs_co_generic_bh_cb, task);
128         qemu_bh_schedule(task->bh);
129     } else {
130         task->complete = 1;
131     }
132 }
133 
134 static int coroutine_fn nfs_co_readv(BlockDriverState *bs,
135                                      int64_t sector_num, int nb_sectors,
136                                      QEMUIOVector *iov)
137 {
138     NFSClient *client = bs->opaque;
139     NFSRPC task;
140 
141     nfs_co_init_task(client, &task);
142     task.iov = iov;
143 
144     if (nfs_pread_async(client->context, client->fh,
145                         sector_num * BDRV_SECTOR_SIZE,
146                         nb_sectors * BDRV_SECTOR_SIZE,
147                         nfs_co_generic_cb, &task) != 0) {
148         return -ENOMEM;
149     }
150 
151     while (!task.complete) {
152         nfs_set_events(client);
153         qemu_coroutine_yield();
154     }
155 
156     if (task.ret < 0) {
157         return task.ret;
158     }
159 
160     /* zero pad short reads */
161     if (task.ret < iov->size) {
162         qemu_iovec_memset(iov, task.ret, 0, iov->size - task.ret);
163     }
164 
165     return 0;
166 }
167 
168 static int coroutine_fn nfs_co_writev(BlockDriverState *bs,
169                                         int64_t sector_num, int nb_sectors,
170                                         QEMUIOVector *iov)
171 {
172     NFSClient *client = bs->opaque;
173     NFSRPC task;
174     char *buf = NULL;
175 
176     nfs_co_init_task(client, &task);
177 
178     buf = g_try_malloc(nb_sectors * BDRV_SECTOR_SIZE);
179     if (nb_sectors && buf == NULL) {
180         return -ENOMEM;
181     }
182 
183     qemu_iovec_to_buf(iov, 0, buf, nb_sectors * BDRV_SECTOR_SIZE);
184 
185     if (nfs_pwrite_async(client->context, client->fh,
186                          sector_num * BDRV_SECTOR_SIZE,
187                          nb_sectors * BDRV_SECTOR_SIZE,
188                          buf, nfs_co_generic_cb, &task) != 0) {
189         g_free(buf);
190         return -ENOMEM;
191     }
192 
193     while (!task.complete) {
194         nfs_set_events(client);
195         qemu_coroutine_yield();
196     }
197 
198     g_free(buf);
199 
200     if (task.ret != nb_sectors * BDRV_SECTOR_SIZE) {
201         return task.ret < 0 ? task.ret : -EIO;
202     }
203 
204     return 0;
205 }
206 
207 static int coroutine_fn nfs_co_flush(BlockDriverState *bs)
208 {
209     NFSClient *client = bs->opaque;
210     NFSRPC task;
211 
212     nfs_co_init_task(client, &task);
213 
214     if (nfs_fsync_async(client->context, client->fh, nfs_co_generic_cb,
215                         &task) != 0) {
216         return -ENOMEM;
217     }
218 
219     while (!task.complete) {
220         nfs_set_events(client);
221         qemu_coroutine_yield();
222     }
223 
224     return task.ret;
225 }
226 
227 /* TODO Convert to fine grained options */
228 static QemuOptsList runtime_opts = {
229     .name = "nfs",
230     .head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
231     .desc = {
232         {
233             .name = "filename",
234             .type = QEMU_OPT_STRING,
235             .help = "URL to the NFS file",
236         },
237         { /* end of list */ }
238     },
239 };
240 
241 static void nfs_detach_aio_context(BlockDriverState *bs)
242 {
243     NFSClient *client = bs->opaque;
244 
245     aio_set_fd_handler(client->aio_context,
246                        nfs_get_fd(client->context),
247                        NULL, NULL, NULL);
248     client->events = 0;
249 }
250 
251 static void nfs_attach_aio_context(BlockDriverState *bs,
252                                    AioContext *new_context)
253 {
254     NFSClient *client = bs->opaque;
255 
256     client->aio_context = new_context;
257     nfs_set_events(client);
258 }
259 
260 static void nfs_client_close(NFSClient *client)
261 {
262     if (client->context) {
263         if (client->fh) {
264             nfs_close(client->context, client->fh);
265         }
266         aio_set_fd_handler(client->aio_context,
267                            nfs_get_fd(client->context),
268                            NULL, NULL, NULL);
269         nfs_destroy_context(client->context);
270     }
271     memset(client, 0, sizeof(NFSClient));
272 }
273 
274 static void nfs_file_close(BlockDriverState *bs)
275 {
276     NFSClient *client = bs->opaque;
277     nfs_client_close(client);
278 }
279 
280 static int64_t nfs_client_open(NFSClient *client, const char *filename,
281                                int flags, Error **errp)
282 {
283     int ret = -EINVAL, i;
284     struct stat st;
285     URI *uri;
286     QueryParams *qp = NULL;
287     char *file = NULL, *strp = NULL;
288 
289     uri = uri_parse(filename);
290     if (!uri) {
291         error_setg(errp, "Invalid URL specified");
292         goto fail;
293     }
294     if (!uri->server) {
295         error_setg(errp, "Invalid URL specified");
296         goto fail;
297     }
298     strp = strrchr(uri->path, '/');
299     if (strp == NULL) {
300         error_setg(errp, "Invalid URL specified");
301         goto fail;
302     }
303     file = g_strdup(strp);
304     *strp = 0;
305 
306     client->context = nfs_init_context();
307     if (client->context == NULL) {
308         error_setg(errp, "Failed to init NFS context");
309         goto fail;
310     }
311 
312     qp = query_params_parse(uri->query);
313     for (i = 0; i < qp->n; i++) {
314         unsigned long long val;
315         if (!qp->p[i].value) {
316             error_setg(errp, "Value for NFS parameter expected: %s",
317                        qp->p[i].name);
318             goto fail;
319         }
320         if (parse_uint_full(qp->p[i].value, &val, 0)) {
321             error_setg(errp, "Illegal value for NFS parameter: %s",
322                        qp->p[i].name);
323             goto fail;
324         }
325         if (!strcmp(qp->p[i].name, "uid")) {
326             nfs_set_uid(client->context, val);
327         } else if (!strcmp(qp->p[i].name, "gid")) {
328             nfs_set_gid(client->context, val);
329         } else if (!strcmp(qp->p[i].name, "tcp-syncnt")) {
330             nfs_set_tcp_syncnt(client->context, val);
331 #ifdef LIBNFS_FEATURE_READAHEAD
332         } else if (!strcmp(qp->p[i].name, "readahead")) {
333             if (val > QEMU_NFS_MAX_READAHEAD_SIZE) {
334                 error_report("NFS Warning: Truncating NFS readahead"
335                              " size to %d", QEMU_NFS_MAX_READAHEAD_SIZE);
336                 val = QEMU_NFS_MAX_READAHEAD_SIZE;
337             }
338             nfs_set_readahead(client->context, val);
339 #endif
340         } else {
341             error_setg(errp, "Unknown NFS parameter name: %s",
342                        qp->p[i].name);
343             goto fail;
344         }
345     }
346 
347     ret = nfs_mount(client->context, uri->server, uri->path);
348     if (ret < 0) {
349         error_setg(errp, "Failed to mount nfs share: %s",
350                    nfs_get_error(client->context));
351         goto fail;
352     }
353 
354     if (flags & O_CREAT) {
355         ret = nfs_creat(client->context, file, 0600, &client->fh);
356         if (ret < 0) {
357             error_setg(errp, "Failed to create file: %s",
358                        nfs_get_error(client->context));
359             goto fail;
360         }
361     } else {
362         ret = nfs_open(client->context, file, flags, &client->fh);
363         if (ret < 0) {
364             error_setg(errp, "Failed to open file : %s",
365                        nfs_get_error(client->context));
366             goto fail;
367         }
368     }
369 
370     ret = nfs_fstat(client->context, client->fh, &st);
371     if (ret < 0) {
372         error_setg(errp, "Failed to fstat file: %s",
373                    nfs_get_error(client->context));
374         goto fail;
375     }
376 
377     ret = DIV_ROUND_UP(st.st_size, BDRV_SECTOR_SIZE);
378     client->st_blocks = st.st_blocks;
379     client->has_zero_init = S_ISREG(st.st_mode);
380     goto out;
381 fail:
382     nfs_client_close(client);
383 out:
384     if (qp) {
385         query_params_free(qp);
386     }
387     uri_free(uri);
388     g_free(file);
389     return ret;
390 }
391 
392 static int nfs_file_open(BlockDriverState *bs, QDict *options, int flags,
393                          Error **errp) {
394     NFSClient *client = bs->opaque;
395     int64_t ret;
396     QemuOpts *opts;
397     Error *local_err = NULL;
398 
399     client->aio_context = bdrv_get_aio_context(bs);
400 
401     opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
402     qemu_opts_absorb_qdict(opts, options, &local_err);
403     if (local_err) {
404         error_propagate(errp, local_err);
405         ret = -EINVAL;
406         goto out;
407     }
408     ret = nfs_client_open(client, qemu_opt_get(opts, "filename"),
409                           (flags & BDRV_O_RDWR) ? O_RDWR : O_RDONLY,
410                           errp);
411     if (ret < 0) {
412         goto out;
413     }
414     bs->total_sectors = ret;
415     ret = 0;
416 out:
417     qemu_opts_del(opts);
418     return ret;
419 }
420 
421 static QemuOptsList nfs_create_opts = {
422     .name = "nfs-create-opts",
423     .head = QTAILQ_HEAD_INITIALIZER(nfs_create_opts.head),
424     .desc = {
425         {
426             .name = BLOCK_OPT_SIZE,
427             .type = QEMU_OPT_SIZE,
428             .help = "Virtual disk size"
429         },
430         { /* end of list */ }
431     }
432 };
433 
434 static int nfs_file_create(const char *url, QemuOpts *opts, Error **errp)
435 {
436     int ret = 0;
437     int64_t total_size = 0;
438     NFSClient *client = g_new0(NFSClient, 1);
439 
440     client->aio_context = qemu_get_aio_context();
441 
442     /* Read out options */
443     total_size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
444                           BDRV_SECTOR_SIZE);
445 
446     ret = nfs_client_open(client, url, O_CREAT, errp);
447     if (ret < 0) {
448         goto out;
449     }
450     ret = nfs_ftruncate(client->context, client->fh, total_size);
451     nfs_client_close(client);
452 out:
453     g_free(client);
454     return ret;
455 }
456 
457 static int nfs_has_zero_init(BlockDriverState *bs)
458 {
459     NFSClient *client = bs->opaque;
460     return client->has_zero_init;
461 }
462 
463 static int64_t nfs_get_allocated_file_size(BlockDriverState *bs)
464 {
465     NFSClient *client = bs->opaque;
466     NFSRPC task = {0};
467     struct stat st;
468 
469     if (bdrv_is_read_only(bs) &&
470         !(bs->open_flags & BDRV_O_NOCACHE)) {
471         return client->st_blocks * 512;
472     }
473 
474     task.st = &st;
475     if (nfs_fstat_async(client->context, client->fh, nfs_co_generic_cb,
476                         &task) != 0) {
477         return -ENOMEM;
478     }
479 
480     while (!task.complete) {
481         nfs_set_events(client);
482         aio_poll(client->aio_context, true);
483     }
484 
485     return (task.ret < 0 ? task.ret : st.st_blocks * 512);
486 }
487 
488 static int nfs_file_truncate(BlockDriverState *bs, int64_t offset)
489 {
490     NFSClient *client = bs->opaque;
491     return nfs_ftruncate(client->context, client->fh, offset);
492 }
493 
494 /* Note that this will not re-establish a connection with the NFS server
495  * - it is effectively a NOP.  */
496 static int nfs_reopen_prepare(BDRVReopenState *state,
497                               BlockReopenQueue *queue, Error **errp)
498 {
499     NFSClient *client = state->bs->opaque;
500     struct stat st;
501     int ret = 0;
502 
503     if (state->flags & BDRV_O_RDWR && bdrv_is_read_only(state->bs)) {
504         error_setg(errp, "Cannot open a read-only mount as read-write");
505         return -EACCES;
506     }
507 
508     /* Update cache for read-only reopens */
509     if (!(state->flags & BDRV_O_RDWR)) {
510         ret = nfs_fstat(client->context, client->fh, &st);
511         if (ret < 0) {
512             error_setg(errp, "Failed to fstat file: %s",
513                        nfs_get_error(client->context));
514             return ret;
515         }
516         client->st_blocks = st.st_blocks;
517     }
518 
519     return 0;
520 }
521 
522 static BlockDriver bdrv_nfs = {
523     .format_name                    = "nfs",
524     .protocol_name                  = "nfs",
525 
526     .instance_size                  = sizeof(NFSClient),
527     .bdrv_needs_filename            = true,
528     .create_opts                    = &nfs_create_opts,
529 
530     .bdrv_has_zero_init             = nfs_has_zero_init,
531     .bdrv_get_allocated_file_size   = nfs_get_allocated_file_size,
532     .bdrv_truncate                  = nfs_file_truncate,
533 
534     .bdrv_file_open                 = nfs_file_open,
535     .bdrv_close                     = nfs_file_close,
536     .bdrv_create                    = nfs_file_create,
537     .bdrv_reopen_prepare            = nfs_reopen_prepare,
538 
539     .bdrv_co_readv                  = nfs_co_readv,
540     .bdrv_co_writev                 = nfs_co_writev,
541     .bdrv_co_flush_to_disk          = nfs_co_flush,
542 
543     .bdrv_detach_aio_context        = nfs_detach_aio_context,
544     .bdrv_attach_aio_context        = nfs_attach_aio_context,
545 };
546 
547 static void nfs_block_init(void)
548 {
549     bdrv_register(&bdrv_nfs);
550 }
551 
552 block_init(nfs_block_init);
553