xref: /openbmc/qemu/block/nfs.c (revision b23197f9)
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 "qemu/osdep.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 #define QEMU_NFS_MAX_DEBUG_LEVEL 2
40 
41 typedef struct NFSClient {
42     struct nfs_context *context;
43     struct nfsfh *fh;
44     int events;
45     bool has_zero_init;
46     AioContext *aio_context;
47     blkcnt_t st_blocks;
48 } NFSClient;
49 
50 typedef struct NFSRPC {
51     int ret;
52     int complete;
53     QEMUIOVector *iov;
54     struct stat *st;
55     Coroutine *co;
56     QEMUBH *bh;
57     NFSClient *client;
58 } NFSRPC;
59 
60 static void nfs_process_read(void *arg);
61 static void nfs_process_write(void *arg);
62 
63 static void nfs_set_events(NFSClient *client)
64 {
65     int ev = nfs_which_events(client->context);
66     if (ev != client->events) {
67         aio_set_fd_handler(client->aio_context, nfs_get_fd(client->context),
68                            false,
69                            (ev & POLLIN) ? nfs_process_read : NULL,
70                            (ev & POLLOUT) ? nfs_process_write : NULL, 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, nfs_get_fd(client->context),
246                        false, 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, nfs_get_fd(client->context),
266                            false, NULL, NULL, NULL);
267         nfs_destroy_context(client->context);
268     }
269     memset(client, 0, sizeof(NFSClient));
270 }
271 
272 static void nfs_file_close(BlockDriverState *bs)
273 {
274     NFSClient *client = bs->opaque;
275     nfs_client_close(client);
276 }
277 
278 static int64_t nfs_client_open(NFSClient *client, const char *filename,
279                                int flags, Error **errp)
280 {
281     int ret = -EINVAL, i;
282     struct stat st;
283     URI *uri;
284     QueryParams *qp = NULL;
285     char *file = NULL, *strp = NULL;
286 
287     uri = uri_parse(filename);
288     if (!uri) {
289         error_setg(errp, "Invalid URL specified");
290         goto fail;
291     }
292     if (!uri->server) {
293         error_setg(errp, "Invalid URL specified");
294         goto fail;
295     }
296     strp = strrchr(uri->path, '/');
297     if (strp == NULL) {
298         error_setg(errp, "Invalid URL specified");
299         goto fail;
300     }
301     file = g_strdup(strp);
302     *strp = 0;
303 
304     client->context = nfs_init_context();
305     if (client->context == NULL) {
306         error_setg(errp, "Failed to init NFS context");
307         goto fail;
308     }
309 
310     qp = query_params_parse(uri->query);
311     for (i = 0; i < qp->n; i++) {
312         unsigned long long val;
313         if (!qp->p[i].value) {
314             error_setg(errp, "Value for NFS parameter expected: %s",
315                        qp->p[i].name);
316             goto fail;
317         }
318         if (parse_uint_full(qp->p[i].value, &val, 0)) {
319             error_setg(errp, "Illegal value for NFS parameter: %s",
320                        qp->p[i].name);
321             goto fail;
322         }
323         if (!strcmp(qp->p[i].name, "uid")) {
324             nfs_set_uid(client->context, val);
325         } else if (!strcmp(qp->p[i].name, "gid")) {
326             nfs_set_gid(client->context, val);
327         } else if (!strcmp(qp->p[i].name, "tcp-syncnt")) {
328             nfs_set_tcp_syncnt(client->context, val);
329 #ifdef LIBNFS_FEATURE_READAHEAD
330         } else if (!strcmp(qp->p[i].name, "readahead")) {
331             if (val > QEMU_NFS_MAX_READAHEAD_SIZE) {
332                 error_report("NFS Warning: Truncating NFS readahead"
333                              " size to %d", QEMU_NFS_MAX_READAHEAD_SIZE);
334                 val = QEMU_NFS_MAX_READAHEAD_SIZE;
335             }
336             nfs_set_readahead(client->context, val);
337 #endif
338 #ifdef LIBNFS_FEATURE_DEBUG
339         } else if (!strcmp(qp->p[i].name, "debug")) {
340             /* limit the maximum debug level to avoid potential flooding
341              * of our log files. */
342             if (val > QEMU_NFS_MAX_DEBUG_LEVEL) {
343                 error_report("NFS Warning: Limiting NFS debug level"
344                              " to %d", QEMU_NFS_MAX_DEBUG_LEVEL);
345                 val = QEMU_NFS_MAX_DEBUG_LEVEL;
346             }
347             nfs_set_debug(client->context, val);
348 #endif
349         } else {
350             error_setg(errp, "Unknown NFS parameter name: %s",
351                        qp->p[i].name);
352             goto fail;
353         }
354     }
355 
356     ret = nfs_mount(client->context, uri->server, uri->path);
357     if (ret < 0) {
358         error_setg(errp, "Failed to mount nfs share: %s",
359                    nfs_get_error(client->context));
360         goto fail;
361     }
362 
363     if (flags & O_CREAT) {
364         ret = nfs_creat(client->context, file, 0600, &client->fh);
365         if (ret < 0) {
366             error_setg(errp, "Failed to create file: %s",
367                        nfs_get_error(client->context));
368             goto fail;
369         }
370     } else {
371         ret = nfs_open(client->context, file, flags, &client->fh);
372         if (ret < 0) {
373             error_setg(errp, "Failed to open file : %s",
374                        nfs_get_error(client->context));
375             goto fail;
376         }
377     }
378 
379     ret = nfs_fstat(client->context, client->fh, &st);
380     if (ret < 0) {
381         error_setg(errp, "Failed to fstat file: %s",
382                    nfs_get_error(client->context));
383         goto fail;
384     }
385 
386     ret = DIV_ROUND_UP(st.st_size, BDRV_SECTOR_SIZE);
387     client->st_blocks = st.st_blocks;
388     client->has_zero_init = S_ISREG(st.st_mode);
389     goto out;
390 fail:
391     nfs_client_close(client);
392 out:
393     if (qp) {
394         query_params_free(qp);
395     }
396     uri_free(uri);
397     g_free(file);
398     return ret;
399 }
400 
401 static int nfs_file_open(BlockDriverState *bs, QDict *options, int flags,
402                          Error **errp) {
403     NFSClient *client = bs->opaque;
404     int64_t ret;
405     QemuOpts *opts;
406     Error *local_err = NULL;
407 
408     client->aio_context = bdrv_get_aio_context(bs);
409 
410     opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
411     qemu_opts_absorb_qdict(opts, options, &local_err);
412     if (local_err) {
413         error_propagate(errp, local_err);
414         ret = -EINVAL;
415         goto out;
416     }
417     ret = nfs_client_open(client, qemu_opt_get(opts, "filename"),
418                           (flags & BDRV_O_RDWR) ? O_RDWR : O_RDONLY,
419                           errp);
420     if (ret < 0) {
421         goto out;
422     }
423     bs->total_sectors = ret;
424     ret = 0;
425 out:
426     qemu_opts_del(opts);
427     return ret;
428 }
429 
430 static QemuOptsList nfs_create_opts = {
431     .name = "nfs-create-opts",
432     .head = QTAILQ_HEAD_INITIALIZER(nfs_create_opts.head),
433     .desc = {
434         {
435             .name = BLOCK_OPT_SIZE,
436             .type = QEMU_OPT_SIZE,
437             .help = "Virtual disk size"
438         },
439         { /* end of list */ }
440     }
441 };
442 
443 static int nfs_file_create(const char *url, QemuOpts *opts, Error **errp)
444 {
445     int ret = 0;
446     int64_t total_size = 0;
447     NFSClient *client = g_new0(NFSClient, 1);
448 
449     client->aio_context = qemu_get_aio_context();
450 
451     /* Read out options */
452     total_size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
453                           BDRV_SECTOR_SIZE);
454 
455     ret = nfs_client_open(client, url, O_CREAT, errp);
456     if (ret < 0) {
457         goto out;
458     }
459     ret = nfs_ftruncate(client->context, client->fh, total_size);
460     nfs_client_close(client);
461 out:
462     g_free(client);
463     return ret;
464 }
465 
466 static int nfs_has_zero_init(BlockDriverState *bs)
467 {
468     NFSClient *client = bs->opaque;
469     return client->has_zero_init;
470 }
471 
472 static int64_t nfs_get_allocated_file_size(BlockDriverState *bs)
473 {
474     NFSClient *client = bs->opaque;
475     NFSRPC task = {0};
476     struct stat st;
477 
478     if (bdrv_is_read_only(bs) &&
479         !(bs->open_flags & BDRV_O_NOCACHE)) {
480         return client->st_blocks * 512;
481     }
482 
483     task.st = &st;
484     if (nfs_fstat_async(client->context, client->fh, nfs_co_generic_cb,
485                         &task) != 0) {
486         return -ENOMEM;
487     }
488 
489     while (!task.complete) {
490         nfs_set_events(client);
491         aio_poll(client->aio_context, true);
492     }
493 
494     return (task.ret < 0 ? task.ret : st.st_blocks * 512);
495 }
496 
497 static int nfs_file_truncate(BlockDriverState *bs, int64_t offset)
498 {
499     NFSClient *client = bs->opaque;
500     return nfs_ftruncate(client->context, client->fh, offset);
501 }
502 
503 /* Note that this will not re-establish a connection with the NFS server
504  * - it is effectively a NOP.  */
505 static int nfs_reopen_prepare(BDRVReopenState *state,
506                               BlockReopenQueue *queue, Error **errp)
507 {
508     NFSClient *client = state->bs->opaque;
509     struct stat st;
510     int ret = 0;
511 
512     if (state->flags & BDRV_O_RDWR && bdrv_is_read_only(state->bs)) {
513         error_setg(errp, "Cannot open a read-only mount as read-write");
514         return -EACCES;
515     }
516 
517     /* Update cache for read-only reopens */
518     if (!(state->flags & BDRV_O_RDWR)) {
519         ret = nfs_fstat(client->context, client->fh, &st);
520         if (ret < 0) {
521             error_setg(errp, "Failed to fstat file: %s",
522                        nfs_get_error(client->context));
523             return ret;
524         }
525         client->st_blocks = st.st_blocks;
526     }
527 
528     return 0;
529 }
530 
531 static BlockDriver bdrv_nfs = {
532     .format_name                    = "nfs",
533     .protocol_name                  = "nfs",
534 
535     .instance_size                  = sizeof(NFSClient),
536     .bdrv_needs_filename            = true,
537     .create_opts                    = &nfs_create_opts,
538 
539     .bdrv_has_zero_init             = nfs_has_zero_init,
540     .bdrv_get_allocated_file_size   = nfs_get_allocated_file_size,
541     .bdrv_truncate                  = nfs_file_truncate,
542 
543     .bdrv_file_open                 = nfs_file_open,
544     .bdrv_close                     = nfs_file_close,
545     .bdrv_create                    = nfs_file_create,
546     .bdrv_reopen_prepare            = nfs_reopen_prepare,
547 
548     .bdrv_co_readv                  = nfs_co_readv,
549     .bdrv_co_writev                 = nfs_co_writev,
550     .bdrv_co_flush_to_disk          = nfs_co_flush,
551 
552     .bdrv_detach_aio_context        = nfs_detach_aio_context,
553     .bdrv_attach_aio_context        = nfs_attach_aio_context,
554 };
555 
556 static void nfs_block_init(void)
557 {
558     bdrv_register(&bdrv_nfs);
559 }
560 
561 block_init(nfs_block_init);
562