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