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