qemu/hw/9pfs/9p-local.c
<<
>>
Prefs
   1/*
   2 * 9p Posix callback
   3 *
   4 * Copyright IBM, Corp. 2010
   5 *
   6 * Authors:
   7 *  Anthony Liguori   <aliguori@us.ibm.com>
   8 *
   9 * This work is licensed under the terms of the GNU GPL, version 2.  See
  10 * the COPYING file in the top-level directory.
  11 */
  12
  13/*
  14 * Not so fast! You might want to read the 9p developer docs first:
  15 * https://wiki.qemu.org/Documentation/9p
  16 */
  17
  18#include "qemu/osdep.h"
  19#include "9p.h"
  20#include "9p-local.h"
  21#include "9p-xattr.h"
  22#include "9p-util.h"
  23#include "fsdev/qemu-fsdev.h"   /* local_ops */
  24#include <arpa/inet.h>
  25#include <pwd.h>
  26#include <grp.h>
  27#include <sys/socket.h>
  28#include <sys/un.h>
  29#include "qemu/xattr.h"
  30#include "qapi/error.h"
  31#include "qemu/cutils.h"
  32#include "qemu/error-report.h"
  33#include "qemu/option.h"
  34#include <libgen.h>
  35#include <linux/fs.h>
  36#ifdef CONFIG_LINUX_MAGIC_H
  37#include <linux/magic.h>
  38#endif
  39#include <sys/ioctl.h>
  40
  41#ifndef XFS_SUPER_MAGIC
  42#define XFS_SUPER_MAGIC  0x58465342
  43#endif
  44#ifndef EXT2_SUPER_MAGIC
  45#define EXT2_SUPER_MAGIC 0xEF53
  46#endif
  47#ifndef REISERFS_SUPER_MAGIC
  48#define REISERFS_SUPER_MAGIC 0x52654973
  49#endif
  50#ifndef BTRFS_SUPER_MAGIC
  51#define BTRFS_SUPER_MAGIC 0x9123683E
  52#endif
  53
  54typedef struct {
  55    int mountfd;
  56} LocalData;
  57
  58int local_open_nofollow(FsContext *fs_ctx, const char *path, int flags,
  59                        mode_t mode)
  60{
  61    LocalData *data = fs_ctx->private;
  62    int fd = data->mountfd;
  63
  64    while (*path && fd != -1) {
  65        const char *c;
  66        int next_fd;
  67        char *head;
  68
  69        /* Only relative paths without consecutive slashes */
  70        assert(*path != '/');
  71
  72        head = g_strdup(path);
  73        c = qemu_strchrnul(path, '/');
  74        if (*c) {
  75            /* Intermediate path element */
  76            head[c - path] = 0;
  77            path = c + 1;
  78            next_fd = openat_dir(fd, head);
  79        } else {
  80            /* Rightmost path element */
  81            next_fd = openat_file(fd, head, flags, mode);
  82            path = c;
  83        }
  84        g_free(head);
  85        if (fd != data->mountfd) {
  86            close_preserve_errno(fd);
  87        }
  88        fd = next_fd;
  89    }
  90
  91    assert(fd != data->mountfd);
  92    return fd;
  93}
  94
  95int local_opendir_nofollow(FsContext *fs_ctx, const char *path)
  96{
  97    return local_open_nofollow(fs_ctx, path, O_DIRECTORY | O_RDONLY, 0);
  98}
  99
 100static void renameat_preserve_errno(int odirfd, const char *opath, int ndirfd,
 101                                    const char *npath)
 102{
 103    int serrno = errno;
 104    renameat(odirfd, opath, ndirfd, npath);
 105    errno = serrno;
 106}
 107
 108static void unlinkat_preserve_errno(int dirfd, const char *path, int flags)
 109{
 110    int serrno = errno;
 111    unlinkat(dirfd, path, flags);
 112    errno = serrno;
 113}
 114
 115#define VIRTFS_META_DIR ".virtfs_metadata"
 116#define VIRTFS_META_ROOT_FILE VIRTFS_META_DIR "_root"
 117
 118static FILE *local_fopenat(int dirfd, const char *name, const char *mode)
 119{
 120    int fd, o_mode = 0;
 121    FILE *fp;
 122    int flags;
 123    /*
 124     * only supports two modes
 125     */
 126    if (mode[0] == 'r') {
 127        flags = O_RDONLY;
 128    } else if (mode[0] == 'w') {
 129        flags = O_WRONLY | O_TRUNC | O_CREAT;
 130        o_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
 131    } else {
 132        return NULL;
 133    }
 134    fd = openat_file(dirfd, name, flags, o_mode);
 135    if (fd == -1) {
 136        return NULL;
 137    }
 138    fp = fdopen(fd, mode);
 139    if (!fp) {
 140        close(fd);
 141    }
 142    return fp;
 143}
 144
 145#define ATTR_MAX 100
 146static void local_mapped_file_attr(int dirfd, const char *name,
 147                                   struct stat *stbuf)
 148{
 149    FILE *fp;
 150    char buf[ATTR_MAX];
 151    int map_dirfd;
 152
 153    if (strcmp(name, ".")) {
 154        map_dirfd = openat_dir(dirfd, VIRTFS_META_DIR);
 155        if (map_dirfd == -1) {
 156            return;
 157        }
 158
 159        fp = local_fopenat(map_dirfd, name, "r");
 160        close_preserve_errno(map_dirfd);
 161    } else {
 162        fp = local_fopenat(dirfd, VIRTFS_META_ROOT_FILE, "r");
 163    }
 164    if (!fp) {
 165        return;
 166    }
 167    memset(buf, 0, ATTR_MAX);
 168    while (fgets(buf, ATTR_MAX, fp)) {
 169        if (!strncmp(buf, "virtfs.uid", 10)) {
 170            stbuf->st_uid = atoi(buf + 11);
 171        } else if (!strncmp(buf, "virtfs.gid", 10)) {
 172            stbuf->st_gid = atoi(buf + 11);
 173        } else if (!strncmp(buf, "virtfs.mode", 11)) {
 174            stbuf->st_mode = atoi(buf + 12);
 175        } else if (!strncmp(buf, "virtfs.rdev", 11)) {
 176            stbuf->st_rdev = atoi(buf + 12);
 177        }
 178        memset(buf, 0, ATTR_MAX);
 179    }
 180    fclose(fp);
 181}
 182
 183static int local_lstat(FsContext *fs_ctx, V9fsPath *fs_path, struct stat *stbuf)
 184{
 185    int err = -1;
 186    char *dirpath = g_path_get_dirname(fs_path->data);
 187    char *name = g_path_get_basename(fs_path->data);
 188    int dirfd;
 189
 190    dirfd = local_opendir_nofollow(fs_ctx, dirpath);
 191    if (dirfd == -1) {
 192        goto out;
 193    }
 194
 195    err = fstatat(dirfd, name, stbuf, AT_SYMLINK_NOFOLLOW);
 196    if (err) {
 197        goto err_out;
 198    }
 199    if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
 200        /* Actual credentials are part of extended attrs */
 201        uid_t tmp_uid;
 202        gid_t tmp_gid;
 203        mode_t tmp_mode;
 204        dev_t tmp_dev;
 205
 206        if (fgetxattrat_nofollow(dirfd, name, "user.virtfs.uid", &tmp_uid,
 207                                 sizeof(uid_t)) > 0) {
 208            stbuf->st_uid = le32_to_cpu(tmp_uid);
 209        }
 210        if (fgetxattrat_nofollow(dirfd, name, "user.virtfs.gid", &tmp_gid,
 211                                 sizeof(gid_t)) > 0) {
 212            stbuf->st_gid = le32_to_cpu(tmp_gid);
 213        }
 214        if (fgetxattrat_nofollow(dirfd, name, "user.virtfs.mode", &tmp_mode,
 215                                 sizeof(mode_t)) > 0) {
 216            stbuf->st_mode = le32_to_cpu(tmp_mode);
 217        }
 218        if (fgetxattrat_nofollow(dirfd, name, "user.virtfs.rdev", &tmp_dev,
 219                                 sizeof(dev_t)) > 0) {
 220            stbuf->st_rdev = le64_to_cpu(tmp_dev);
 221        }
 222    } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
 223        local_mapped_file_attr(dirfd, name, stbuf);
 224    }
 225
 226err_out:
 227    close_preserve_errno(dirfd);
 228out:
 229    g_free(name);
 230    g_free(dirpath);
 231    return err;
 232}
 233
 234static int local_set_mapped_file_attrat(int dirfd, const char *name,
 235                                        FsCred *credp)
 236{
 237    FILE *fp;
 238    int ret;
 239    char buf[ATTR_MAX];
 240    int uid = -1, gid = -1, mode = -1, rdev = -1;
 241    int map_dirfd = -1, map_fd;
 242    bool is_root = !strcmp(name, ".");
 243
 244    if (is_root) {
 245        fp = local_fopenat(dirfd, VIRTFS_META_ROOT_FILE, "r");
 246        if (!fp) {
 247            if (errno == ENOENT) {
 248                goto update_map_file;
 249            } else {
 250                return -1;
 251            }
 252        }
 253    } else {
 254        ret = mkdirat(dirfd, VIRTFS_META_DIR, 0700);
 255        if (ret < 0 && errno != EEXIST) {
 256            return -1;
 257        }
 258
 259        map_dirfd = openat_dir(dirfd, VIRTFS_META_DIR);
 260        if (map_dirfd == -1) {
 261            return -1;
 262        }
 263
 264        fp = local_fopenat(map_dirfd, name, "r");
 265        if (!fp) {
 266            if (errno == ENOENT) {
 267                goto update_map_file;
 268            } else {
 269                close_preserve_errno(map_dirfd);
 270                return -1;
 271            }
 272        }
 273    }
 274    memset(buf, 0, ATTR_MAX);
 275    while (fgets(buf, ATTR_MAX, fp)) {
 276        if (!strncmp(buf, "virtfs.uid", 10)) {
 277            uid = atoi(buf + 11);
 278        } else if (!strncmp(buf, "virtfs.gid", 10)) {
 279            gid = atoi(buf + 11);
 280        } else if (!strncmp(buf, "virtfs.mode", 11)) {
 281            mode = atoi(buf + 12);
 282        } else if (!strncmp(buf, "virtfs.rdev", 11)) {
 283            rdev = atoi(buf + 12);
 284        }
 285        memset(buf, 0, ATTR_MAX);
 286    }
 287    fclose(fp);
 288
 289update_map_file:
 290    if (is_root) {
 291        fp = local_fopenat(dirfd, VIRTFS_META_ROOT_FILE, "w");
 292    } else {
 293        fp = local_fopenat(map_dirfd, name, "w");
 294        /* We can't go this far with map_dirfd not being a valid file descriptor
 295         * but some versions of gcc aren't smart enough to see it.
 296         */
 297        if (map_dirfd != -1) {
 298            close_preserve_errno(map_dirfd);
 299        }
 300    }
 301    if (!fp) {
 302        return -1;
 303    }
 304
 305    map_fd = fileno(fp);
 306    assert(map_fd != -1);
 307    ret = fchmod(map_fd, 0600);
 308    assert(ret == 0);
 309
 310    if (credp->fc_uid != -1) {
 311        uid = credp->fc_uid;
 312    }
 313    if (credp->fc_gid != -1) {
 314        gid = credp->fc_gid;
 315    }
 316    if (credp->fc_mode != (mode_t)-1) {
 317        mode = credp->fc_mode;
 318    }
 319    if (credp->fc_rdev != -1) {
 320        rdev = credp->fc_rdev;
 321    }
 322
 323    if (uid != -1) {
 324        fprintf(fp, "virtfs.uid=%d\n", uid);
 325    }
 326    if (gid != -1) {
 327        fprintf(fp, "virtfs.gid=%d\n", gid);
 328    }
 329    if (mode != -1) {
 330        fprintf(fp, "virtfs.mode=%d\n", mode);
 331    }
 332    if (rdev != -1) {
 333        fprintf(fp, "virtfs.rdev=%d\n", rdev);
 334    }
 335    fclose(fp);
 336
 337    return 0;
 338}
 339
 340static int fchmodat_nofollow(int dirfd, const char *name, mode_t mode)
 341{
 342    struct stat stbuf;
 343    int fd, ret;
 344
 345    /* FIXME: this should be handled with fchmodat(AT_SYMLINK_NOFOLLOW).
 346     * Unfortunately, the linux kernel doesn't implement it yet.
 347     */
 348
 349     /* First, we clear non-racing symlinks out of the way. */
 350    if (fstatat(dirfd, name, &stbuf, AT_SYMLINK_NOFOLLOW)) {
 351        return -1;
 352    }
 353    if (S_ISLNK(stbuf.st_mode)) {
 354        errno = ELOOP;
 355        return -1;
 356    }
 357
 358    fd = openat_file(dirfd, name, O_RDONLY | O_PATH_9P_UTIL | O_NOFOLLOW, 0);
 359#if O_PATH_9P_UTIL == 0
 360    /* Fallback for systems that don't support O_PATH: we depend on the file
 361     * being readable or writable.
 362     */
 363    if (fd == -1) {
 364        /* In case the file is writable-only and isn't a directory. */
 365        if (errno == EACCES) {
 366            fd = openat_file(dirfd, name, O_WRONLY, 0);
 367        }
 368        if (fd == -1 && errno == EISDIR) {
 369            errno = EACCES;
 370        }
 371    }
 372    if (fd == -1) {
 373        return -1;
 374    }
 375    ret = fchmod(fd, mode);
 376#else
 377    /* Access modes are ignored when O_PATH is supported. If name is a symbolic
 378     * link, O_PATH | O_NOFOLLOW causes openat(2) to return a file descriptor
 379     * referring to the symbolic link.
 380     */
 381    if (fd == -1) {
 382        return -1;
 383    }
 384
 385    /* Now we handle racing symlinks. */
 386    ret = fstat(fd, &stbuf);
 387    if (!ret) {
 388        if (S_ISLNK(stbuf.st_mode)) {
 389            errno = ELOOP;
 390            ret = -1;
 391        } else {
 392            char *proc_path = g_strdup_printf("/proc/self/fd/%d", fd);
 393            ret = chmod(proc_path, mode);
 394            g_free(proc_path);
 395        }
 396    }
 397#endif
 398    close_preserve_errno(fd);
 399    return ret;
 400}
 401
 402static int local_set_xattrat(int dirfd, const char *path, FsCred *credp)
 403{
 404    int err;
 405
 406    if (credp->fc_uid != -1) {
 407        uint32_t tmp_uid = cpu_to_le32(credp->fc_uid);
 408        err = fsetxattrat_nofollow(dirfd, path, "user.virtfs.uid", &tmp_uid,
 409                                   sizeof(uid_t), 0);
 410        if (err) {
 411            return err;
 412        }
 413    }
 414    if (credp->fc_gid != -1) {
 415        uint32_t tmp_gid = cpu_to_le32(credp->fc_gid);
 416        err = fsetxattrat_nofollow(dirfd, path, "user.virtfs.gid", &tmp_gid,
 417                                   sizeof(gid_t), 0);
 418        if (err) {
 419            return err;
 420        }
 421    }
 422    if (credp->fc_mode != (mode_t)-1) {
 423        uint32_t tmp_mode = cpu_to_le32(credp->fc_mode);
 424        err = fsetxattrat_nofollow(dirfd, path, "user.virtfs.mode", &tmp_mode,
 425                                   sizeof(mode_t), 0);
 426        if (err) {
 427            return err;
 428        }
 429    }
 430    if (credp->fc_rdev != -1) {
 431        uint64_t tmp_rdev = cpu_to_le64(credp->fc_rdev);
 432        err = fsetxattrat_nofollow(dirfd, path, "user.virtfs.rdev", &tmp_rdev,
 433                                   sizeof(dev_t), 0);
 434        if (err) {
 435            return err;
 436        }
 437    }
 438    return 0;
 439}
 440
 441static int local_set_cred_passthrough(FsContext *fs_ctx, int dirfd,
 442                                      const char *name, FsCred *credp)
 443{
 444    if (fchownat(dirfd, name, credp->fc_uid, credp->fc_gid,
 445                 AT_SYMLINK_NOFOLLOW) < 0) {
 446        /*
 447         * If we fail to change ownership and if we are
 448         * using security model none. Ignore the error
 449         */
 450        if ((fs_ctx->export_flags & V9FS_SEC_MASK) != V9FS_SM_NONE) {
 451            return -1;
 452        }
 453    }
 454
 455    return fchmodat_nofollow(dirfd, name, credp->fc_mode & 07777);
 456}
 457
 458static ssize_t local_readlink(FsContext *fs_ctx, V9fsPath *fs_path,
 459                              char *buf, size_t bufsz)
 460{
 461    ssize_t tsize = -1;
 462
 463    if ((fs_ctx->export_flags & V9FS_SM_MAPPED) ||
 464        (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE)) {
 465        int fd;
 466
 467        fd = local_open_nofollow(fs_ctx, fs_path->data, O_RDONLY, 0);
 468        if (fd == -1) {
 469            return -1;
 470        }
 471        do {
 472            tsize = read(fd, (void *)buf, bufsz);
 473        } while (tsize == -1 && errno == EINTR);
 474        close_preserve_errno(fd);
 475    } else if ((fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) ||
 476               (fs_ctx->export_flags & V9FS_SM_NONE)) {
 477        char *dirpath = g_path_get_dirname(fs_path->data);
 478        char *name = g_path_get_basename(fs_path->data);
 479        int dirfd;
 480
 481        dirfd = local_opendir_nofollow(fs_ctx, dirpath);
 482        if (dirfd == -1) {
 483            goto out;
 484        }
 485
 486        tsize = readlinkat(dirfd, name, buf, bufsz);
 487        close_preserve_errno(dirfd);
 488    out:
 489        g_free(name);
 490        g_free(dirpath);
 491    }
 492    return tsize;
 493}
 494
 495static int local_close(FsContext *ctx, V9fsFidOpenState *fs)
 496{
 497    return close(fs->fd);
 498}
 499
 500static int local_closedir(FsContext *ctx, V9fsFidOpenState *fs)
 501{
 502    return closedir(fs->dir.stream);
 503}
 504
 505static int local_open(FsContext *ctx, V9fsPath *fs_path,
 506                      int flags, V9fsFidOpenState *fs)
 507{
 508    int fd;
 509
 510    fd = local_open_nofollow(ctx, fs_path->data, flags, 0);
 511    if (fd == -1) {
 512        return -1;
 513    }
 514    fs->fd = fd;
 515    return fs->fd;
 516}
 517
 518static int local_opendir(FsContext *ctx,
 519                         V9fsPath *fs_path, V9fsFidOpenState *fs)
 520{
 521    int dirfd;
 522    DIR *stream;
 523
 524    dirfd = local_opendir_nofollow(ctx, fs_path->data);
 525    if (dirfd == -1) {
 526        return -1;
 527    }
 528
 529    stream = fdopendir(dirfd);
 530    if (!stream) {
 531        close(dirfd);
 532        return -1;
 533    }
 534    fs->dir.stream = stream;
 535    return 0;
 536}
 537
 538static void local_rewinddir(FsContext *ctx, V9fsFidOpenState *fs)
 539{
 540    rewinddir(fs->dir.stream);
 541}
 542
 543static off_t local_telldir(FsContext *ctx, V9fsFidOpenState *fs)
 544{
 545    return telldir(fs->dir.stream);
 546}
 547
 548static bool local_is_mapped_file_metadata(FsContext *fs_ctx, const char *name)
 549{
 550    return
 551        !strcmp(name, VIRTFS_META_DIR) || !strcmp(name, VIRTFS_META_ROOT_FILE);
 552}
 553
 554static struct dirent *local_readdir(FsContext *ctx, V9fsFidOpenState *fs)
 555{
 556    struct dirent *entry;
 557
 558again:
 559    entry = readdir(fs->dir.stream);
 560    if (!entry) {
 561        return NULL;
 562    }
 563
 564    if (ctx->export_flags & V9FS_SM_MAPPED) {
 565        entry->d_type = DT_UNKNOWN;
 566    } else if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
 567        if (local_is_mapped_file_metadata(ctx, entry->d_name)) {
 568            /* skip the meta data */
 569            goto again;
 570        }
 571        entry->d_type = DT_UNKNOWN;
 572    }
 573
 574    return entry;
 575}
 576
 577static void local_seekdir(FsContext *ctx, V9fsFidOpenState *fs, off_t off)
 578{
 579    seekdir(fs->dir.stream, off);
 580}
 581
 582static ssize_t local_preadv(FsContext *ctx, V9fsFidOpenState *fs,
 583                            const struct iovec *iov,
 584                            int iovcnt, off_t offset)
 585{
 586#ifdef CONFIG_PREADV
 587    return preadv(fs->fd, iov, iovcnt, offset);
 588#else
 589    int err = lseek(fs->fd, offset, SEEK_SET);
 590    if (err == -1) {
 591        return err;
 592    } else {
 593        return readv(fs->fd, iov, iovcnt);
 594    }
 595#endif
 596}
 597
 598static ssize_t local_pwritev(FsContext *ctx, V9fsFidOpenState *fs,
 599                             const struct iovec *iov,
 600                             int iovcnt, off_t offset)
 601{
 602    ssize_t ret;
 603#ifdef CONFIG_PREADV
 604    ret = pwritev(fs->fd, iov, iovcnt, offset);
 605#else
 606    int err = lseek(fs->fd, offset, SEEK_SET);
 607    if (err == -1) {
 608        return err;
 609    } else {
 610        ret = writev(fs->fd, iov, iovcnt);
 611    }
 612#endif
 613#ifdef CONFIG_SYNC_FILE_RANGE
 614    if (ret > 0 && ctx->export_flags & V9FS_IMMEDIATE_WRITEOUT) {
 615        /*
 616         * Initiate a writeback. This is not a data integrity sync.
 617         * We want to ensure that we don't leave dirty pages in the cache
 618         * after write when writeout=immediate is sepcified.
 619         */
 620        sync_file_range(fs->fd, offset, ret,
 621                        SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE);
 622    }
 623#endif
 624    return ret;
 625}
 626
 627static int local_chmod(FsContext *fs_ctx, V9fsPath *fs_path, FsCred *credp)
 628{
 629    char *dirpath = g_path_get_dirname(fs_path->data);
 630    char *name = g_path_get_basename(fs_path->data);
 631    int ret = -1;
 632    int dirfd;
 633
 634    dirfd = local_opendir_nofollow(fs_ctx, dirpath);
 635    if (dirfd == -1) {
 636        goto out;
 637    }
 638
 639    if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
 640        ret = local_set_xattrat(dirfd, name, credp);
 641    } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
 642        ret = local_set_mapped_file_attrat(dirfd, name, credp);
 643    } else if (fs_ctx->export_flags & V9FS_SM_PASSTHROUGH ||
 644               fs_ctx->export_flags & V9FS_SM_NONE) {
 645        ret = fchmodat_nofollow(dirfd, name, credp->fc_mode);
 646    }
 647    close_preserve_errno(dirfd);
 648
 649out:
 650    g_free(dirpath);
 651    g_free(name);
 652    return ret;
 653}
 654
 655static int local_mknod(FsContext *fs_ctx, V9fsPath *dir_path,
 656                       const char *name, FsCred *credp)
 657{
 658    int err = -1;
 659    int dirfd;
 660
 661    if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE &&
 662        local_is_mapped_file_metadata(fs_ctx, name)) {
 663        errno = EINVAL;
 664        return -1;
 665    }
 666
 667    dirfd = local_opendir_nofollow(fs_ctx, dir_path->data);
 668    if (dirfd == -1) {
 669        return -1;
 670    }
 671
 672    if (fs_ctx->export_flags & V9FS_SM_MAPPED ||
 673        fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
 674        err = mknodat(dirfd, name, fs_ctx->fmode | S_IFREG, 0);
 675        if (err == -1) {
 676            goto out;
 677        }
 678
 679        if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
 680            err = local_set_xattrat(dirfd, name, credp);
 681        } else {
 682            err = local_set_mapped_file_attrat(dirfd, name, credp);
 683        }
 684        if (err == -1) {
 685            goto err_end;
 686        }
 687    } else if (fs_ctx->export_flags & V9FS_SM_PASSTHROUGH ||
 688               fs_ctx->export_flags & V9FS_SM_NONE) {
 689        err = mknodat(dirfd, name, credp->fc_mode, credp->fc_rdev);
 690        if (err == -1) {
 691            goto out;
 692        }
 693        err = local_set_cred_passthrough(fs_ctx, dirfd, name, credp);
 694        if (err == -1) {
 695            goto err_end;
 696        }
 697    }
 698    goto out;
 699
 700err_end:
 701    unlinkat_preserve_errno(dirfd, name, 0);
 702out:
 703    close_preserve_errno(dirfd);
 704    return err;
 705}
 706
 707static int local_mkdir(FsContext *fs_ctx, V9fsPath *dir_path,
 708                       const char *name, FsCred *credp)
 709{
 710    int err = -1;
 711    int dirfd;
 712
 713    if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE &&
 714        local_is_mapped_file_metadata(fs_ctx, name)) {
 715        errno = EINVAL;
 716        return -1;
 717    }
 718
 719    dirfd = local_opendir_nofollow(fs_ctx, dir_path->data);
 720    if (dirfd == -1) {
 721        return -1;
 722    }
 723
 724    if (fs_ctx->export_flags & V9FS_SM_MAPPED ||
 725        fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
 726        err = mkdirat(dirfd, name, fs_ctx->dmode);
 727        if (err == -1) {
 728            goto out;
 729        }
 730        credp->fc_mode = credp->fc_mode | S_IFDIR;
 731
 732        if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
 733            err = local_set_xattrat(dirfd, name, credp);
 734        } else {
 735            err = local_set_mapped_file_attrat(dirfd, name, credp);
 736        }
 737        if (err == -1) {
 738            goto err_end;
 739        }
 740    } else if (fs_ctx->export_flags & V9FS_SM_PASSTHROUGH ||
 741               fs_ctx->export_flags & V9FS_SM_NONE) {
 742        err = mkdirat(dirfd, name, credp->fc_mode);
 743        if (err == -1) {
 744            goto out;
 745        }
 746        err = local_set_cred_passthrough(fs_ctx, dirfd, name, credp);
 747        if (err == -1) {
 748            goto err_end;
 749        }
 750    }
 751    goto out;
 752
 753err_end:
 754    unlinkat_preserve_errno(dirfd, name, AT_REMOVEDIR);
 755out:
 756    close_preserve_errno(dirfd);
 757    return err;
 758}
 759
 760static int local_fstat(FsContext *fs_ctx, int fid_type,
 761                       V9fsFidOpenState *fs, struct stat *stbuf)
 762{
 763    int err, fd;
 764
 765    if (fid_type == P9_FID_DIR) {
 766        fd = dirfd(fs->dir.stream);
 767    } else {
 768        fd = fs->fd;
 769    }
 770
 771    err = fstat(fd, stbuf);
 772    if (err) {
 773        return err;
 774    }
 775    if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
 776        /* Actual credentials are part of extended attrs */
 777        uid_t tmp_uid;
 778        gid_t tmp_gid;
 779        mode_t tmp_mode;
 780        dev_t tmp_dev;
 781
 782        if (fgetxattr(fd, "user.virtfs.uid", &tmp_uid, sizeof(uid_t)) > 0) {
 783            stbuf->st_uid = le32_to_cpu(tmp_uid);
 784        }
 785        if (fgetxattr(fd, "user.virtfs.gid", &tmp_gid, sizeof(gid_t)) > 0) {
 786            stbuf->st_gid = le32_to_cpu(tmp_gid);
 787        }
 788        if (fgetxattr(fd, "user.virtfs.mode", &tmp_mode, sizeof(mode_t)) > 0) {
 789            stbuf->st_mode = le32_to_cpu(tmp_mode);
 790        }
 791        if (fgetxattr(fd, "user.virtfs.rdev", &tmp_dev, sizeof(dev_t)) > 0) {
 792            stbuf->st_rdev = le64_to_cpu(tmp_dev);
 793        }
 794    } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
 795        errno = EOPNOTSUPP;
 796        return -1;
 797    }
 798    return err;
 799}
 800
 801static int local_open2(FsContext *fs_ctx, V9fsPath *dir_path, const char *name,
 802                       int flags, FsCred *credp, V9fsFidOpenState *fs)
 803{
 804    int fd = -1;
 805    int err = -1;
 806    int dirfd;
 807
 808    if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE &&
 809        local_is_mapped_file_metadata(fs_ctx, name)) {
 810        errno = EINVAL;
 811        return -1;
 812    }
 813
 814    /*
 815     * Mark all the open to not follow symlinks
 816     */
 817    flags |= O_NOFOLLOW;
 818
 819    dirfd = local_opendir_nofollow(fs_ctx, dir_path->data);
 820    if (dirfd == -1) {
 821        return -1;
 822    }
 823
 824    /* Determine the security model */
 825    if (fs_ctx->export_flags & V9FS_SM_MAPPED ||
 826        fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
 827        fd = openat_file(dirfd, name, flags, fs_ctx->fmode);
 828        if (fd == -1) {
 829            goto out;
 830        }
 831        credp->fc_mode = credp->fc_mode | S_IFREG;
 832        if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
 833            /* Set cleint credentials in xattr */
 834            err = local_set_xattrat(dirfd, name, credp);
 835        } else {
 836            err = local_set_mapped_file_attrat(dirfd, name, credp);
 837        }
 838        if (err == -1) {
 839            goto err_end;
 840        }
 841    } else if ((fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) ||
 842               (fs_ctx->export_flags & V9FS_SM_NONE)) {
 843        fd = openat_file(dirfd, name, flags, credp->fc_mode);
 844        if (fd == -1) {
 845            goto out;
 846        }
 847        err = local_set_cred_passthrough(fs_ctx, dirfd, name, credp);
 848        if (err == -1) {
 849            goto err_end;
 850        }
 851    }
 852    err = fd;
 853    fs->fd = fd;
 854    goto out;
 855
 856err_end:
 857    unlinkat_preserve_errno(dirfd, name,
 858                            flags & O_DIRECTORY ? AT_REMOVEDIR : 0);
 859    close_preserve_errno(fd);
 860out:
 861    close_preserve_errno(dirfd);
 862    return err;
 863}
 864
 865
 866static int local_symlink(FsContext *fs_ctx, const char *oldpath,
 867                         V9fsPath *dir_path, const char *name, FsCred *credp)
 868{
 869    int err = -1;
 870    int dirfd;
 871
 872    if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE &&
 873        local_is_mapped_file_metadata(fs_ctx, name)) {
 874        errno = EINVAL;
 875        return -1;
 876    }
 877
 878    dirfd = local_opendir_nofollow(fs_ctx, dir_path->data);
 879    if (dirfd == -1) {
 880        return -1;
 881    }
 882
 883    /* Determine the security model */
 884    if (fs_ctx->export_flags & V9FS_SM_MAPPED ||
 885        fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
 886        int fd;
 887        ssize_t oldpath_size, write_size;
 888
 889        fd = openat_file(dirfd, name, O_CREAT | O_EXCL | O_RDWR,
 890                         fs_ctx->fmode);
 891        if (fd == -1) {
 892            goto out;
 893        }
 894        /* Write the oldpath (target) to the file. */
 895        oldpath_size = strlen(oldpath);
 896        do {
 897            write_size = write(fd, (void *)oldpath, oldpath_size);
 898        } while (write_size == -1 && errno == EINTR);
 899        close_preserve_errno(fd);
 900
 901        if (write_size != oldpath_size) {
 902            goto err_end;
 903        }
 904        /* Set cleint credentials in symlink's xattr */
 905        credp->fc_mode = credp->fc_mode | S_IFLNK;
 906
 907        if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
 908            err = local_set_xattrat(dirfd, name, credp);
 909        } else {
 910            err = local_set_mapped_file_attrat(dirfd, name, credp);
 911        }
 912        if (err == -1) {
 913            goto err_end;
 914        }
 915    } else if (fs_ctx->export_flags & V9FS_SM_PASSTHROUGH ||
 916               fs_ctx->export_flags & V9FS_SM_NONE) {
 917        err = symlinkat(oldpath, dirfd, name);
 918        if (err) {
 919            goto out;
 920        }
 921        err = fchownat(dirfd, name, credp->fc_uid, credp->fc_gid,
 922                       AT_SYMLINK_NOFOLLOW);
 923        if (err == -1) {
 924            /*
 925             * If we fail to change ownership and if we are
 926             * using security model none. Ignore the error
 927             */
 928            if ((fs_ctx->export_flags & V9FS_SEC_MASK) != V9FS_SM_NONE) {
 929                goto err_end;
 930            } else {
 931                err = 0;
 932            }
 933        }
 934    }
 935    goto out;
 936
 937err_end:
 938    unlinkat_preserve_errno(dirfd, name, 0);
 939out:
 940    close_preserve_errno(dirfd);
 941    return err;
 942}
 943
 944static int local_link(FsContext *ctx, V9fsPath *oldpath,
 945                      V9fsPath *dirpath, const char *name)
 946{
 947    char *odirpath = g_path_get_dirname(oldpath->data);
 948    char *oname = g_path_get_basename(oldpath->data);
 949    int ret = -1;
 950    int odirfd, ndirfd;
 951
 952    if (ctx->export_flags & V9FS_SM_MAPPED_FILE &&
 953        local_is_mapped_file_metadata(ctx, name)) {
 954        errno = EINVAL;
 955        goto out;
 956    }
 957
 958    odirfd = local_opendir_nofollow(ctx, odirpath);
 959    if (odirfd == -1) {
 960        goto out;
 961    }
 962
 963    ndirfd = local_opendir_nofollow(ctx, dirpath->data);
 964    if (ndirfd == -1) {
 965        close_preserve_errno(odirfd);
 966        goto out;
 967    }
 968
 969    ret = linkat(odirfd, oname, ndirfd, name, 0);
 970    if (ret < 0) {
 971        goto out_close;
 972    }
 973
 974    /* now link the virtfs_metadata files */
 975    if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
 976        int omap_dirfd, nmap_dirfd;
 977
 978        ret = mkdirat(ndirfd, VIRTFS_META_DIR, 0700);
 979        if (ret < 0 && errno != EEXIST) {
 980            goto err_undo_link;
 981        }
 982
 983        omap_dirfd = openat_dir(odirfd, VIRTFS_META_DIR);
 984        if (omap_dirfd == -1) {
 985            goto err;
 986        }
 987
 988        nmap_dirfd = openat_dir(ndirfd, VIRTFS_META_DIR);
 989        if (nmap_dirfd == -1) {
 990            close_preserve_errno(omap_dirfd);
 991            goto err;
 992        }
 993
 994        ret = linkat(omap_dirfd, oname, nmap_dirfd, name, 0);
 995        close_preserve_errno(nmap_dirfd);
 996        close_preserve_errno(omap_dirfd);
 997        if (ret < 0 && errno != ENOENT) {
 998            goto err_undo_link;
 999        }
1000
1001        ret = 0;
1002    }
1003    goto out_close;
1004
1005err:
1006    ret = -1;
1007err_undo_link:
1008    unlinkat_preserve_errno(ndirfd, name, 0);
1009out_close:
1010    close_preserve_errno(ndirfd);
1011    close_preserve_errno(odirfd);
1012out:
1013    g_free(oname);
1014    g_free(odirpath);
1015    return ret;
1016}
1017
1018static int local_truncate(FsContext *ctx, V9fsPath *fs_path, off_t size)
1019{
1020    int fd, ret;
1021
1022    fd = local_open_nofollow(ctx, fs_path->data, O_WRONLY, 0);
1023    if (fd == -1) {
1024        return -1;
1025    }
1026    ret = ftruncate(fd, size);
1027    close_preserve_errno(fd);
1028    return ret;
1029}
1030
1031static int local_chown(FsContext *fs_ctx, V9fsPath *fs_path, FsCred *credp)
1032{
1033    char *dirpath = g_path_get_dirname(fs_path->data);
1034    char *name = g_path_get_basename(fs_path->data);
1035    int ret = -1;
1036    int dirfd;
1037
1038    dirfd = local_opendir_nofollow(fs_ctx, dirpath);
1039    if (dirfd == -1) {
1040        goto out;
1041    }
1042
1043    if ((credp->fc_uid == -1 && credp->fc_gid == -1) ||
1044        (fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) ||
1045        (fs_ctx->export_flags & V9FS_SM_NONE)) {
1046        ret = fchownat(dirfd, name, credp->fc_uid, credp->fc_gid,
1047                       AT_SYMLINK_NOFOLLOW);
1048    } else if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
1049        ret = local_set_xattrat(dirfd, name, credp);
1050    } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
1051        ret = local_set_mapped_file_attrat(dirfd, name, credp);
1052    }
1053
1054    close_preserve_errno(dirfd);
1055out:
1056    g_free(name);
1057    g_free(dirpath);
1058    return ret;
1059}
1060
1061static int local_utimensat(FsContext *s, V9fsPath *fs_path,
1062                           const struct timespec *buf)
1063{
1064    char *dirpath = g_path_get_dirname(fs_path->data);
1065    char *name = g_path_get_basename(fs_path->data);
1066    int dirfd, ret = -1;
1067
1068    dirfd = local_opendir_nofollow(s, dirpath);
1069    if (dirfd == -1) {
1070        goto out;
1071    }
1072
1073    ret = utimensat(dirfd, name, buf, AT_SYMLINK_NOFOLLOW);
1074    close_preserve_errno(dirfd);
1075out:
1076    g_free(dirpath);
1077    g_free(name);
1078    return ret;
1079}
1080
1081static int local_unlinkat_common(FsContext *ctx, int dirfd, const char *name,
1082                                 int flags)
1083{
1084    int ret;
1085
1086    if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
1087        int map_dirfd;
1088
1089        /* We need to remove the metadata as well:
1090         * - the metadata directory if we're removing a directory
1091         * - the metadata file in the parent's metadata directory
1092         *
1093         * If any of these are missing (ie, ENOENT) then we're probably
1094         * trying to remove something that wasn't created in mapped-file
1095         * mode. We just ignore the error.
1096         */
1097        if (flags == AT_REMOVEDIR) {
1098            int fd;
1099
1100            fd = openat_dir(dirfd, name);
1101            if (fd == -1) {
1102                return -1;
1103            }
1104            ret = unlinkat(fd, VIRTFS_META_DIR, AT_REMOVEDIR);
1105            close_preserve_errno(fd);
1106            if (ret < 0 && errno != ENOENT) {
1107                return -1;
1108            }
1109        }
1110        map_dirfd = openat_dir(dirfd, VIRTFS_META_DIR);
1111        if (map_dirfd != -1) {
1112            ret = unlinkat(map_dirfd, name, 0);
1113            close_preserve_errno(map_dirfd);
1114            if (ret < 0 && errno != ENOENT) {
1115                return -1;
1116            }
1117        } else if (errno != ENOENT) {
1118            return -1;
1119        }
1120    }
1121
1122    return unlinkat(dirfd, name, flags);
1123}
1124
1125static int local_remove(FsContext *ctx, const char *path)
1126{
1127    struct stat stbuf;
1128    char *dirpath = g_path_get_dirname(path);
1129    char *name = g_path_get_basename(path);
1130    int flags = 0;
1131    int dirfd;
1132    int err = -1;
1133
1134    dirfd = local_opendir_nofollow(ctx, dirpath);
1135    if (dirfd == -1) {
1136        goto out;
1137    }
1138
1139    if (fstatat(dirfd, name, &stbuf, AT_SYMLINK_NOFOLLOW) < 0) {
1140        goto err_out;
1141    }
1142
1143    if (S_ISDIR(stbuf.st_mode)) {
1144        flags |= AT_REMOVEDIR;
1145    }
1146
1147    err = local_unlinkat_common(ctx, dirfd, name, flags);
1148err_out:
1149    close_preserve_errno(dirfd);
1150out:
1151    g_free(name);
1152    g_free(dirpath);
1153    return err;
1154}
1155
1156static int local_fsync(FsContext *ctx, int fid_type,
1157                       V9fsFidOpenState *fs, int datasync)
1158{
1159    int fd;
1160
1161    if (fid_type == P9_FID_DIR) {
1162        fd = dirfd(fs->dir.stream);
1163    } else {
1164        fd = fs->fd;
1165    }
1166
1167    if (datasync) {
1168        return qemu_fdatasync(fd);
1169    } else {
1170        return fsync(fd);
1171    }
1172}
1173
1174static int local_statfs(FsContext *s, V9fsPath *fs_path, struct statfs *stbuf)
1175{
1176    int fd, ret;
1177
1178    fd = local_open_nofollow(s, fs_path->data, O_RDONLY, 0);
1179    if (fd == -1) {
1180        return -1;
1181    }
1182    ret = fstatfs(fd, stbuf);
1183    close_preserve_errno(fd);
1184    return ret;
1185}
1186
1187static ssize_t local_lgetxattr(FsContext *ctx, V9fsPath *fs_path,
1188                               const char *name, void *value, size_t size)
1189{
1190    char *path = fs_path->data;
1191
1192    return v9fs_get_xattr(ctx, path, name, value, size);
1193}
1194
1195static ssize_t local_llistxattr(FsContext *ctx, V9fsPath *fs_path,
1196                                void *value, size_t size)
1197{
1198    char *path = fs_path->data;
1199
1200    return v9fs_list_xattr(ctx, path, value, size);
1201}
1202
1203static int local_lsetxattr(FsContext *ctx, V9fsPath *fs_path, const char *name,
1204                           void *value, size_t size, int flags)
1205{
1206    char *path = fs_path->data;
1207
1208    return v9fs_set_xattr(ctx, path, name, value, size, flags);
1209}
1210
1211static int local_lremovexattr(FsContext *ctx, V9fsPath *fs_path,
1212                              const char *name)
1213{
1214    char *path = fs_path->data;
1215
1216    return v9fs_remove_xattr(ctx, path, name);
1217}
1218
1219static int local_name_to_path(FsContext *ctx, V9fsPath *dir_path,
1220                              const char *name, V9fsPath *target)
1221{
1222    if (ctx->export_flags & V9FS_SM_MAPPED_FILE &&
1223        local_is_mapped_file_metadata(ctx, name)) {
1224        errno = EINVAL;
1225        return -1;
1226    }
1227
1228    if (dir_path) {
1229        if (!strcmp(name, ".")) {
1230            /* "." relative to "foo/bar" is "foo/bar" */
1231            v9fs_path_copy(target, dir_path);
1232        } else if (!strcmp(name, "..")) {
1233            if (!strcmp(dir_path->data, ".")) {
1234                /* ".." relative to the root is "." */
1235                v9fs_path_sprintf(target, ".");
1236            } else {
1237                char *tmp = g_path_get_dirname(dir_path->data);
1238                /* Symbolic links are resolved by the client. We can assume
1239                 * that ".." relative to "foo/bar" is equivalent to "foo"
1240                 */
1241                v9fs_path_sprintf(target, "%s", tmp);
1242                g_free(tmp);
1243            }
1244        } else {
1245            assert(!strchr(name, '/'));
1246            v9fs_path_sprintf(target, "%s/%s", dir_path->data, name);
1247        }
1248    } else if (!strcmp(name, "/") || !strcmp(name, ".") ||
1249               !strcmp(name, "..")) {
1250            /* This is the root fid */
1251        v9fs_path_sprintf(target, ".");
1252    } else {
1253        assert(!strchr(name, '/'));
1254        v9fs_path_sprintf(target, "./%s", name);
1255    }
1256    return 0;
1257}
1258
1259static int local_renameat(FsContext *ctx, V9fsPath *olddir,
1260                          const char *old_name, V9fsPath *newdir,
1261                          const char *new_name)
1262{
1263    int ret;
1264    int odirfd, ndirfd;
1265
1266    if (ctx->export_flags & V9FS_SM_MAPPED_FILE &&
1267        (local_is_mapped_file_metadata(ctx, old_name) ||
1268         local_is_mapped_file_metadata(ctx, new_name))) {
1269        errno = EINVAL;
1270        return -1;
1271    }
1272
1273    odirfd = local_opendir_nofollow(ctx, olddir->data);
1274    if (odirfd == -1) {
1275        return -1;
1276    }
1277
1278    ndirfd = local_opendir_nofollow(ctx, newdir->data);
1279    if (ndirfd == -1) {
1280        close_preserve_errno(odirfd);
1281        return -1;
1282    }
1283
1284    ret = renameat(odirfd, old_name, ndirfd, new_name);
1285    if (ret < 0) {
1286        goto out;
1287    }
1288
1289    if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
1290        int omap_dirfd, nmap_dirfd;
1291
1292        ret = mkdirat(ndirfd, VIRTFS_META_DIR, 0700);
1293        if (ret < 0 && errno != EEXIST) {
1294            goto err_undo_rename;
1295        }
1296
1297        omap_dirfd = openat_dir(odirfd, VIRTFS_META_DIR);
1298        if (omap_dirfd == -1) {
1299            goto err;
1300        }
1301
1302        nmap_dirfd = openat_dir(ndirfd, VIRTFS_META_DIR);
1303        if (nmap_dirfd == -1) {
1304            close_preserve_errno(omap_dirfd);
1305            goto err;
1306        }
1307
1308        /* rename the .virtfs_metadata files */
1309        ret = renameat(omap_dirfd, old_name, nmap_dirfd, new_name);
1310        close_preserve_errno(nmap_dirfd);
1311        close_preserve_errno(omap_dirfd);
1312        if (ret < 0 && errno != ENOENT) {
1313            goto err_undo_rename;
1314        }
1315
1316        ret = 0;
1317    }
1318    goto out;
1319
1320err:
1321    ret = -1;
1322err_undo_rename:
1323    renameat_preserve_errno(ndirfd, new_name, odirfd, old_name);
1324out:
1325    close_preserve_errno(ndirfd);
1326    close_preserve_errno(odirfd);
1327    return ret;
1328}
1329
1330static void v9fs_path_init_dirname(V9fsPath *path, const char *str)
1331{
1332    path->data = g_path_get_dirname(str);
1333    path->size = strlen(path->data) + 1;
1334}
1335
1336static int local_rename(FsContext *ctx, const char *oldpath,
1337                        const char *newpath)
1338{
1339    int err;
1340    char *oname = g_path_get_basename(oldpath);
1341    char *nname = g_path_get_basename(newpath);
1342    V9fsPath olddir, newdir;
1343
1344    v9fs_path_init_dirname(&olddir, oldpath);
1345    v9fs_path_init_dirname(&newdir, newpath);
1346
1347    err = local_renameat(ctx, &olddir, oname, &newdir, nname);
1348
1349    v9fs_path_free(&newdir);
1350    v9fs_path_free(&olddir);
1351    g_free(nname);
1352    g_free(oname);
1353
1354    return err;
1355}
1356
1357static int local_unlinkat(FsContext *ctx, V9fsPath *dir,
1358                          const char *name, int flags)
1359{
1360    int ret;
1361    int dirfd;
1362
1363    if (ctx->export_flags & V9FS_SM_MAPPED_FILE &&
1364        local_is_mapped_file_metadata(ctx, name)) {
1365        errno = EINVAL;
1366        return -1;
1367    }
1368
1369    dirfd = local_opendir_nofollow(ctx, dir->data);
1370    if (dirfd == -1) {
1371        return -1;
1372    }
1373
1374    ret = local_unlinkat_common(ctx, dirfd, name, flags);
1375    close_preserve_errno(dirfd);
1376    return ret;
1377}
1378
1379#ifdef FS_IOC_GETVERSION
1380static int local_ioc_getversion(FsContext *ctx, V9fsPath *path,
1381                                mode_t st_mode, uint64_t *st_gen)
1382{
1383    int err;
1384    V9fsFidOpenState fid_open;
1385
1386    /*
1387     * Do not try to open special files like device nodes, fifos etc
1388     * We can get fd for regular files and directories only
1389     */
1390    if (!S_ISREG(st_mode) && !S_ISDIR(st_mode)) {
1391        errno = ENOTTY;
1392        return -1;
1393    }
1394    err = local_open(ctx, path, O_RDONLY, &fid_open);
1395    if (err < 0) {
1396        return err;
1397    }
1398    err = ioctl(fid_open.fd, FS_IOC_GETVERSION, st_gen);
1399    local_close(ctx, &fid_open);
1400    return err;
1401}
1402#endif
1403
1404static int local_ioc_getversion_init(FsContext *ctx, LocalData *data, Error **errp)
1405{
1406#ifdef FS_IOC_GETVERSION
1407    struct statfs stbuf;
1408
1409    /*
1410     * use ioc_getversion only if the ioctl is definied
1411     */
1412    if (fstatfs(data->mountfd, &stbuf) < 0) {
1413        error_setg_errno(errp, errno,
1414                         "failed to stat file system at '%s'", ctx->fs_root);
1415        return -1;
1416    }
1417    switch (stbuf.f_type) {
1418    case EXT2_SUPER_MAGIC:
1419    case BTRFS_SUPER_MAGIC:
1420    case REISERFS_SUPER_MAGIC:
1421    case XFS_SUPER_MAGIC:
1422        ctx->exops.get_st_gen = local_ioc_getversion;
1423        break;
1424    }
1425#endif
1426    return 0;
1427}
1428
1429static int local_init(FsContext *ctx, Error **errp)
1430{
1431    LocalData *data = g_malloc(sizeof(*data));
1432
1433    data->mountfd = open(ctx->fs_root, O_DIRECTORY | O_RDONLY);
1434    if (data->mountfd == -1) {
1435        error_setg_errno(errp, errno, "failed to open '%s'", ctx->fs_root);
1436        goto err;
1437    }
1438
1439    if (local_ioc_getversion_init(ctx, data, errp) < 0) {
1440        close(data->mountfd);
1441        goto err;
1442    }
1443
1444    if (ctx->export_flags & V9FS_SM_PASSTHROUGH) {
1445        ctx->xops = passthrough_xattr_ops;
1446    } else if (ctx->export_flags & V9FS_SM_MAPPED) {
1447        ctx->xops = mapped_xattr_ops;
1448    } else if (ctx->export_flags & V9FS_SM_NONE) {
1449        ctx->xops = none_xattr_ops;
1450    } else if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
1451        /*
1452         * xattr operation for mapped-file and passthrough
1453         * remain same.
1454         */
1455        ctx->xops = passthrough_xattr_ops;
1456    }
1457    ctx->export_flags |= V9FS_PATHNAME_FSCONTEXT;
1458
1459    ctx->private = data;
1460    return 0;
1461
1462err:
1463    g_free(data);
1464    return -1;
1465}
1466
1467static void local_cleanup(FsContext *ctx)
1468{
1469    LocalData *data = ctx->private;
1470
1471    if (!data) {
1472        return;
1473    }
1474
1475    close(data->mountfd);
1476    g_free(data);
1477}
1478
1479static void error_append_security_model_hint(Error *const *errp)
1480{
1481    error_append_hint(errp, "Valid options are: security_model="
1482                      "[passthrough|mapped-xattr|mapped-file|none]\n");
1483}
1484
1485static int local_parse_opts(QemuOpts *opts, FsDriverEntry *fse, Error **errp)
1486{
1487    ERRP_GUARD();
1488    const char *sec_model = qemu_opt_get(opts, "security_model");
1489    const char *path = qemu_opt_get(opts, "path");
1490    const char *multidevs = qemu_opt_get(opts, "multidevs");
1491
1492    if (!sec_model) {
1493        error_setg(errp, "security_model property not set");
1494        error_append_security_model_hint(errp);
1495        return -1;
1496    }
1497
1498    if (!strcmp(sec_model, "passthrough")) {
1499        fse->export_flags |= V9FS_SM_PASSTHROUGH;
1500    } else if (!strcmp(sec_model, "mapped") ||
1501               !strcmp(sec_model, "mapped-xattr")) {
1502        fse->export_flags |= V9FS_SM_MAPPED;
1503    } else if (!strcmp(sec_model, "none")) {
1504        fse->export_flags |= V9FS_SM_NONE;
1505    } else if (!strcmp(sec_model, "mapped-file")) {
1506        fse->export_flags |= V9FS_SM_MAPPED_FILE;
1507    } else {
1508        error_setg(errp, "invalid security_model property '%s'", sec_model);
1509        error_append_security_model_hint(errp);
1510        return -1;
1511    }
1512
1513    if (multidevs) {
1514        if (!strcmp(multidevs, "remap")) {
1515            fse->export_flags &= ~V9FS_FORBID_MULTIDEVS;
1516            fse->export_flags |= V9FS_REMAP_INODES;
1517        } else if (!strcmp(multidevs, "forbid")) {
1518            fse->export_flags &= ~V9FS_REMAP_INODES;
1519            fse->export_flags |= V9FS_FORBID_MULTIDEVS;
1520        } else if (!strcmp(multidevs, "warn")) {
1521            fse->export_flags &= ~V9FS_FORBID_MULTIDEVS;
1522            fse->export_flags &= ~V9FS_REMAP_INODES;
1523        } else {
1524            error_setg(errp, "invalid multidevs property '%s'",
1525                       multidevs);
1526            error_append_hint(errp, "Valid options are: multidevs="
1527                              "[remap|forbid|warn]\n");
1528            return -1;
1529        }
1530    }
1531
1532    if (!path) {
1533        error_setg(errp, "path property not set");
1534        return -1;
1535    }
1536
1537    if (fsdev_throttle_parse_opts(opts, &fse->fst, errp)) {
1538        error_prepend(errp, "invalid throttle configuration: ");
1539        return -1;
1540    }
1541
1542    if (fse->export_flags & V9FS_SM_MAPPED ||
1543        fse->export_flags & V9FS_SM_MAPPED_FILE) {
1544        fse->fmode =
1545            qemu_opt_get_number(opts, "fmode", SM_LOCAL_MODE_BITS) & 0777;
1546        fse->dmode =
1547            qemu_opt_get_number(opts, "dmode", SM_LOCAL_DIR_MODE_BITS) & 0777;
1548    } else {
1549        if (qemu_opt_find(opts, "fmode")) {
1550            error_setg(errp, "fmode is only valid for mapped security modes");
1551            return -1;
1552        }
1553        if (qemu_opt_find(opts, "dmode")) {
1554            error_setg(errp, "dmode is only valid for mapped security modes");
1555            return -1;
1556        }
1557    }
1558
1559    fse->path = g_strdup(path);
1560
1561    return 0;
1562}
1563
1564FileOperations local_ops = {
1565    .parse_opts = local_parse_opts,
1566    .init  = local_init,
1567    .cleanup = local_cleanup,
1568    .lstat = local_lstat,
1569    .readlink = local_readlink,
1570    .close = local_close,
1571    .closedir = local_closedir,
1572    .open = local_open,
1573    .opendir = local_opendir,
1574    .rewinddir = local_rewinddir,
1575    .telldir = local_telldir,
1576    .readdir = local_readdir,
1577    .seekdir = local_seekdir,
1578    .preadv = local_preadv,
1579    .pwritev = local_pwritev,
1580    .chmod = local_chmod,
1581    .mknod = local_mknod,
1582    .mkdir = local_mkdir,
1583    .fstat = local_fstat,
1584    .open2 = local_open2,
1585    .symlink = local_symlink,
1586    .link = local_link,
1587    .truncate = local_truncate,
1588    .rename = local_rename,
1589    .chown = local_chown,
1590    .utimensat = local_utimensat,
1591    .remove = local_remove,
1592    .fsync = local_fsync,
1593    .statfs = local_statfs,
1594    .lgetxattr = local_lgetxattr,
1595    .llistxattr = local_llistxattr,
1596    .lsetxattr = local_lsetxattr,
1597    .lremovexattr = local_lremovexattr,
1598    .name_to_path = local_name_to_path,
1599    .renameat  = local_renameat,
1600    .unlinkat = local_unlinkat,
1601};
1602