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    fd = openat_file(dirfd, name, O_RDONLY | O_PATH_9P_UTIL | O_NOFOLLOW, 0);
 353#if O_PATH_9P_UTIL == 0
 354    /* Fallback for systems that don't support O_PATH: we depend on the file
 355     * being readable or writable.
 356     */
 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    /* Access modes are ignored when O_PATH is supported. If name is a symbolic
 372     * link, O_PATH | O_NOFOLLOW causes openat(2) to return a file descriptor
 373     * referring to the symbolic link.
 374     */
 375    if (fd == -1) {
 376        return -1;
 377    }
 378
 379    /* Now we handle racing symlinks. */
 380    ret = fstat(fd, &stbuf);
 381    if (!ret) {
 382        if (S_ISLNK(stbuf.st_mode)) {
 383            errno = ELOOP;
 384            ret = -1;
 385        } else {
 386            char *proc_path = g_strdup_printf("/proc/self/fd/%d", fd);
 387            ret = chmod(proc_path, mode);
 388            g_free(proc_path);
 389        }
 390    }
 391#endif
 392    close_preserve_errno(fd);
 393    return ret;
 394}
 395
 396static int local_set_xattrat(int dirfd, const char *path, FsCred *credp)
 397{
 398    int err;
 399
 400    if (credp->fc_uid != -1) {
 401        uint32_t tmp_uid = cpu_to_le32(credp->fc_uid);
 402        err = fsetxattrat_nofollow(dirfd, path, "user.virtfs.uid", &tmp_uid,
 403                                   sizeof(uid_t), 0);
 404        if (err) {
 405            return err;
 406        }
 407    }
 408    if (credp->fc_gid != -1) {
 409        uint32_t tmp_gid = cpu_to_le32(credp->fc_gid);
 410        err = fsetxattrat_nofollow(dirfd, path, "user.virtfs.gid", &tmp_gid,
 411                                   sizeof(gid_t), 0);
 412        if (err) {
 413            return err;
 414        }
 415    }
 416    if (credp->fc_mode != -1) {
 417        uint32_t tmp_mode = cpu_to_le32(credp->fc_mode);
 418        err = fsetxattrat_nofollow(dirfd, path, "user.virtfs.mode", &tmp_mode,
 419                                   sizeof(mode_t), 0);
 420        if (err) {
 421            return err;
 422        }
 423    }
 424    if (credp->fc_rdev != -1) {
 425        uint64_t tmp_rdev = cpu_to_le64(credp->fc_rdev);
 426        err = fsetxattrat_nofollow(dirfd, path, "user.virtfs.rdev", &tmp_rdev,
 427                                   sizeof(dev_t), 0);
 428        if (err) {
 429            return err;
 430        }
 431    }
 432    return 0;
 433}
 434
 435static int local_set_cred_passthrough(FsContext *fs_ctx, int dirfd,
 436                                      const char *name, FsCred *credp)
 437{
 438    if (fchownat(dirfd, name, credp->fc_uid, credp->fc_gid,
 439                 AT_SYMLINK_NOFOLLOW) < 0) {
 440        /*
 441         * If we fail to change ownership and if we are
 442         * using security model none. Ignore the error
 443         */
 444        if ((fs_ctx->export_flags & V9FS_SEC_MASK) != V9FS_SM_NONE) {
 445            return -1;
 446        }
 447    }
 448
 449    return fchmodat_nofollow(dirfd, name, credp->fc_mode & 07777);
 450}
 451
 452static ssize_t local_readlink(FsContext *fs_ctx, V9fsPath *fs_path,
 453                              char *buf, size_t bufsz)
 454{
 455    ssize_t tsize = -1;
 456
 457    if ((fs_ctx->export_flags & V9FS_SM_MAPPED) ||
 458        (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE)) {
 459        int fd;
 460
 461        fd = local_open_nofollow(fs_ctx, fs_path->data, O_RDONLY, 0);
 462        if (fd == -1) {
 463            return -1;
 464        }
 465        do {
 466            tsize = read(fd, (void *)buf, bufsz);
 467        } while (tsize == -1 && errno == EINTR);
 468        close_preserve_errno(fd);
 469    } else if ((fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) ||
 470               (fs_ctx->export_flags & V9FS_SM_NONE)) {
 471        char *dirpath = g_path_get_dirname(fs_path->data);
 472        char *name = g_path_get_basename(fs_path->data);
 473        int dirfd;
 474
 475        dirfd = local_opendir_nofollow(fs_ctx, dirpath);
 476        if (dirfd == -1) {
 477            goto out;
 478        }
 479
 480        tsize = readlinkat(dirfd, name, buf, bufsz);
 481        close_preserve_errno(dirfd);
 482    out:
 483        g_free(name);
 484        g_free(dirpath);
 485    }
 486    return tsize;
 487}
 488
 489static int local_close(FsContext *ctx, V9fsFidOpenState *fs)
 490{
 491    return close(fs->fd);
 492}
 493
 494static int local_closedir(FsContext *ctx, V9fsFidOpenState *fs)
 495{
 496    return closedir(fs->dir.stream);
 497}
 498
 499static int local_open(FsContext *ctx, V9fsPath *fs_path,
 500                      int flags, V9fsFidOpenState *fs)
 501{
 502    int fd;
 503
 504    fd = local_open_nofollow(ctx, fs_path->data, flags, 0);
 505    if (fd == -1) {
 506        return -1;
 507    }
 508    fs->fd = fd;
 509    return fs->fd;
 510}
 511
 512static int local_opendir(FsContext *ctx,
 513                         V9fsPath *fs_path, V9fsFidOpenState *fs)
 514{
 515    int dirfd;
 516    DIR *stream;
 517
 518    dirfd = local_opendir_nofollow(ctx, fs_path->data);
 519    if (dirfd == -1) {
 520        return -1;
 521    }
 522
 523    stream = fdopendir(dirfd);
 524    if (!stream) {
 525        close(dirfd);
 526        return -1;
 527    }
 528    fs->dir.stream = stream;
 529    return 0;
 530}
 531
 532static void local_rewinddir(FsContext *ctx, V9fsFidOpenState *fs)
 533{
 534    rewinddir(fs->dir.stream);
 535}
 536
 537static off_t local_telldir(FsContext *ctx, V9fsFidOpenState *fs)
 538{
 539    return telldir(fs->dir.stream);
 540}
 541
 542static bool local_is_mapped_file_metadata(FsContext *fs_ctx, const char *name)
 543{
 544    return
 545        !strcmp(name, VIRTFS_META_DIR) || !strcmp(name, VIRTFS_META_ROOT_FILE);
 546}
 547
 548static struct dirent *local_readdir(FsContext *ctx, V9fsFidOpenState *fs)
 549{
 550    struct dirent *entry;
 551
 552again:
 553    entry = readdir(fs->dir.stream);
 554    if (!entry) {
 555        return NULL;
 556    }
 557
 558    if (ctx->export_flags & V9FS_SM_MAPPED) {
 559        entry->d_type = DT_UNKNOWN;
 560    } else if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
 561        if (local_is_mapped_file_metadata(ctx, entry->d_name)) {
 562            /* skip the meta data */
 563            goto again;
 564        }
 565        entry->d_type = DT_UNKNOWN;
 566    }
 567
 568    return entry;
 569}
 570
 571static void local_seekdir(FsContext *ctx, V9fsFidOpenState *fs, off_t off)
 572{
 573    seekdir(fs->dir.stream, off);
 574}
 575
 576static ssize_t local_preadv(FsContext *ctx, V9fsFidOpenState *fs,
 577                            const struct iovec *iov,
 578                            int iovcnt, off_t offset)
 579{
 580#ifdef CONFIG_PREADV
 581    return preadv(fs->fd, iov, iovcnt, offset);
 582#else
 583    int err = lseek(fs->fd, offset, SEEK_SET);
 584    if (err == -1) {
 585        return err;
 586    } else {
 587        return readv(fs->fd, iov, iovcnt);
 588    }
 589#endif
 590}
 591
 592static ssize_t local_pwritev(FsContext *ctx, V9fsFidOpenState *fs,
 593                             const struct iovec *iov,
 594                             int iovcnt, off_t offset)
 595{
 596    ssize_t ret;
 597#ifdef CONFIG_PREADV
 598    ret = pwritev(fs->fd, iov, iovcnt, offset);
 599#else
 600    int err = lseek(fs->fd, offset, SEEK_SET);
 601    if (err == -1) {
 602        return err;
 603    } else {
 604        ret = writev(fs->fd, iov, iovcnt);
 605    }
 606#endif
 607#ifdef CONFIG_SYNC_FILE_RANGE
 608    if (ret > 0 && ctx->export_flags & V9FS_IMMEDIATE_WRITEOUT) {
 609        /*
 610         * Initiate a writeback. This is not a data integrity sync.
 611         * We want to ensure that we don't leave dirty pages in the cache
 612         * after write when writeout=immediate is sepcified.
 613         */
 614        sync_file_range(fs->fd, offset, ret,
 615                        SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE);
 616    }
 617#endif
 618    return ret;
 619}
 620
 621static int local_chmod(FsContext *fs_ctx, V9fsPath *fs_path, FsCred *credp)
 622{
 623    char *dirpath = g_path_get_dirname(fs_path->data);
 624    char *name = g_path_get_basename(fs_path->data);
 625    int ret = -1;
 626    int dirfd;
 627
 628    dirfd = local_opendir_nofollow(fs_ctx, dirpath);
 629    if (dirfd == -1) {
 630        goto out;
 631    }
 632
 633    if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
 634        ret = local_set_xattrat(dirfd, name, credp);
 635    } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
 636        ret = local_set_mapped_file_attrat(dirfd, name, credp);
 637    } else if (fs_ctx->export_flags & V9FS_SM_PASSTHROUGH ||
 638               fs_ctx->export_flags & V9FS_SM_NONE) {
 639        ret = fchmodat_nofollow(dirfd, name, credp->fc_mode);
 640    }
 641    close_preserve_errno(dirfd);
 642
 643out:
 644    g_free(dirpath);
 645    g_free(name);
 646    return ret;
 647}
 648
 649static int local_mknod(FsContext *fs_ctx, V9fsPath *dir_path,
 650                       const char *name, FsCred *credp)
 651{
 652    int err = -1;
 653    int dirfd;
 654
 655    if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE &&
 656        local_is_mapped_file_metadata(fs_ctx, name)) {
 657        errno = EINVAL;
 658        return -1;
 659    }
 660
 661    dirfd = local_opendir_nofollow(fs_ctx, dir_path->data);
 662    if (dirfd == -1) {
 663        return -1;
 664    }
 665
 666    if (fs_ctx->export_flags & V9FS_SM_MAPPED ||
 667        fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
 668        err = mknodat(dirfd, name, fs_ctx->fmode | S_IFREG, 0);
 669        if (err == -1) {
 670            goto out;
 671        }
 672
 673        if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
 674            err = local_set_xattrat(dirfd, name, credp);
 675        } else {
 676            err = local_set_mapped_file_attrat(dirfd, name, credp);
 677        }
 678        if (err == -1) {
 679            goto err_end;
 680        }
 681    } else if (fs_ctx->export_flags & V9FS_SM_PASSTHROUGH ||
 682               fs_ctx->export_flags & V9FS_SM_NONE) {
 683        err = mknodat(dirfd, name, credp->fc_mode, credp->fc_rdev);
 684        if (err == -1) {
 685            goto out;
 686        }
 687        err = local_set_cred_passthrough(fs_ctx, dirfd, name, credp);
 688        if (err == -1) {
 689            goto err_end;
 690        }
 691    }
 692    goto out;
 693
 694err_end:
 695    unlinkat_preserve_errno(dirfd, name, 0);
 696out:
 697    close_preserve_errno(dirfd);
 698    return err;
 699}
 700
 701static int local_mkdir(FsContext *fs_ctx, V9fsPath *dir_path,
 702                       const char *name, FsCred *credp)
 703{
 704    int err = -1;
 705    int dirfd;
 706
 707    if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE &&
 708        local_is_mapped_file_metadata(fs_ctx, name)) {
 709        errno = EINVAL;
 710        return -1;
 711    }
 712
 713    dirfd = local_opendir_nofollow(fs_ctx, dir_path->data);
 714    if (dirfd == -1) {
 715        return -1;
 716    }
 717
 718    if (fs_ctx->export_flags & V9FS_SM_MAPPED ||
 719        fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
 720        err = mkdirat(dirfd, name, fs_ctx->dmode);
 721        if (err == -1) {
 722            goto out;
 723        }
 724        credp->fc_mode = credp->fc_mode | S_IFDIR;
 725
 726        if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
 727            err = local_set_xattrat(dirfd, name, credp);
 728        } else {
 729            err = local_set_mapped_file_attrat(dirfd, name, credp);
 730        }
 731        if (err == -1) {
 732            goto err_end;
 733        }
 734    } else if (fs_ctx->export_flags & V9FS_SM_PASSTHROUGH ||
 735               fs_ctx->export_flags & V9FS_SM_NONE) {
 736        err = mkdirat(dirfd, name, credp->fc_mode);
 737        if (err == -1) {
 738            goto out;
 739        }
 740        err = local_set_cred_passthrough(fs_ctx, dirfd, name, credp);
 741        if (err == -1) {
 742            goto err_end;
 743        }
 744    }
 745    goto out;
 746
 747err_end:
 748    unlinkat_preserve_errno(dirfd, name, AT_REMOVEDIR);
 749out:
 750    close_preserve_errno(dirfd);
 751    return err;
 752}
 753
 754static int local_fstat(FsContext *fs_ctx, int fid_type,
 755                       V9fsFidOpenState *fs, struct stat *stbuf)
 756{
 757    int err, fd;
 758
 759    if (fid_type == P9_FID_DIR) {
 760        fd = dirfd(fs->dir.stream);
 761    } else {
 762        fd = fs->fd;
 763    }
 764
 765    err = fstat(fd, stbuf);
 766    if (err) {
 767        return err;
 768    }
 769    if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
 770        /* Actual credentials are part of extended attrs */
 771        uid_t tmp_uid;
 772        gid_t tmp_gid;
 773        mode_t tmp_mode;
 774        dev_t tmp_dev;
 775
 776        if (fgetxattr(fd, "user.virtfs.uid", &tmp_uid, sizeof(uid_t)) > 0) {
 777            stbuf->st_uid = le32_to_cpu(tmp_uid);
 778        }
 779        if (fgetxattr(fd, "user.virtfs.gid", &tmp_gid, sizeof(gid_t)) > 0) {
 780            stbuf->st_gid = le32_to_cpu(tmp_gid);
 781        }
 782        if (fgetxattr(fd, "user.virtfs.mode", &tmp_mode, sizeof(mode_t)) > 0) {
 783            stbuf->st_mode = le32_to_cpu(tmp_mode);
 784        }
 785        if (fgetxattr(fd, "user.virtfs.rdev", &tmp_dev, sizeof(dev_t)) > 0) {
 786            stbuf->st_rdev = le64_to_cpu(tmp_dev);
 787        }
 788    } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
 789        errno = EOPNOTSUPP;
 790        return -1;
 791    }
 792    return err;
 793}
 794
 795static int local_open2(FsContext *fs_ctx, V9fsPath *dir_path, const char *name,
 796                       int flags, FsCred *credp, V9fsFidOpenState *fs)
 797{
 798    int fd = -1;
 799    int err = -1;
 800    int dirfd;
 801
 802    if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE &&
 803        local_is_mapped_file_metadata(fs_ctx, name)) {
 804        errno = EINVAL;
 805        return -1;
 806    }
 807
 808    /*
 809     * Mark all the open to not follow symlinks
 810     */
 811    flags |= O_NOFOLLOW;
 812
 813    dirfd = local_opendir_nofollow(fs_ctx, dir_path->data);
 814    if (dirfd == -1) {
 815        return -1;
 816    }
 817
 818    /* Determine the security model */
 819    if (fs_ctx->export_flags & V9FS_SM_MAPPED ||
 820        fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
 821        fd = openat_file(dirfd, name, flags, fs_ctx->fmode);
 822        if (fd == -1) {
 823            goto out;
 824        }
 825        credp->fc_mode = credp->fc_mode|S_IFREG;
 826        if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
 827            /* Set cleint credentials in xattr */
 828            err = local_set_xattrat(dirfd, name, credp);
 829        } else {
 830            err = local_set_mapped_file_attrat(dirfd, name, credp);
 831        }
 832        if (err == -1) {
 833            goto err_end;
 834        }
 835    } else if ((fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) ||
 836               (fs_ctx->export_flags & V9FS_SM_NONE)) {
 837        fd = openat_file(dirfd, name, flags, credp->fc_mode);
 838        if (fd == -1) {
 839            goto out;
 840        }
 841        err = local_set_cred_passthrough(fs_ctx, dirfd, name, credp);
 842        if (err == -1) {
 843            goto err_end;
 844        }
 845    }
 846    err = fd;
 847    fs->fd = fd;
 848    goto out;
 849
 850err_end:
 851    unlinkat_preserve_errno(dirfd, name,
 852                            flags & O_DIRECTORY ? AT_REMOVEDIR : 0);
 853    close_preserve_errno(fd);
 854out:
 855    close_preserve_errno(dirfd);
 856    return err;
 857}
 858
 859
 860static int local_symlink(FsContext *fs_ctx, const char *oldpath,
 861                         V9fsPath *dir_path, const char *name, FsCred *credp)
 862{
 863    int err = -1;
 864    int dirfd;
 865
 866    if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE &&
 867        local_is_mapped_file_metadata(fs_ctx, name)) {
 868        errno = EINVAL;
 869        return -1;
 870    }
 871
 872    dirfd = local_opendir_nofollow(fs_ctx, dir_path->data);
 873    if (dirfd == -1) {
 874        return -1;
 875    }
 876
 877    /* Determine the security model */
 878    if (fs_ctx->export_flags & V9FS_SM_MAPPED ||
 879        fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
 880        int fd;
 881        ssize_t oldpath_size, write_size;
 882
 883        fd = openat_file(dirfd, name, O_CREAT | O_EXCL | O_RDWR,
 884                         fs_ctx->fmode);
 885        if (fd == -1) {
 886            goto out;
 887        }
 888        /* Write the oldpath (target) to the file. */
 889        oldpath_size = strlen(oldpath);
 890        do {
 891            write_size = write(fd, (void *)oldpath, oldpath_size);
 892        } while (write_size == -1 && errno == EINTR);
 893        close_preserve_errno(fd);
 894
 895        if (write_size != oldpath_size) {
 896            goto err_end;
 897        }
 898        /* Set cleint credentials in symlink's xattr */
 899        credp->fc_mode = credp->fc_mode | S_IFLNK;
 900
 901        if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
 902            err = local_set_xattrat(dirfd, name, credp);
 903        } else {
 904            err = local_set_mapped_file_attrat(dirfd, name, credp);
 905        }
 906        if (err == -1) {
 907            goto err_end;
 908        }
 909    } else if (fs_ctx->export_flags & V9FS_SM_PASSTHROUGH ||
 910               fs_ctx->export_flags & V9FS_SM_NONE) {
 911        err = symlinkat(oldpath, dirfd, name);
 912        if (err) {
 913            goto out;
 914        }
 915        err = fchownat(dirfd, name, credp->fc_uid, credp->fc_gid,
 916                       AT_SYMLINK_NOFOLLOW);
 917        if (err == -1) {
 918            /*
 919             * If we fail to change ownership and if we are
 920             * using security model none. Ignore the error
 921             */
 922            if ((fs_ctx->export_flags & V9FS_SEC_MASK) != V9FS_SM_NONE) {
 923                goto err_end;
 924            } else {
 925                err = 0;
 926            }
 927        }
 928    }
 929    goto out;
 930
 931err_end:
 932    unlinkat_preserve_errno(dirfd, name, 0);
 933out:
 934    close_preserve_errno(dirfd);
 935    return err;
 936}
 937
 938static int local_link(FsContext *ctx, V9fsPath *oldpath,
 939                      V9fsPath *dirpath, const char *name)
 940{
 941    char *odirpath = g_path_get_dirname(oldpath->data);
 942    char *oname = g_path_get_basename(oldpath->data);
 943    int ret = -1;
 944    int odirfd, ndirfd;
 945
 946    if (ctx->export_flags & V9FS_SM_MAPPED_FILE &&
 947        local_is_mapped_file_metadata(ctx, name)) {
 948        errno = EINVAL;
 949        return -1;
 950    }
 951
 952    odirfd = local_opendir_nofollow(ctx, odirpath);
 953    if (odirfd == -1) {
 954        goto out;
 955    }
 956
 957    ndirfd = local_opendir_nofollow(ctx, dirpath->data);
 958    if (ndirfd == -1) {
 959        close_preserve_errno(odirfd);
 960        goto out;
 961    }
 962
 963    ret = linkat(odirfd, oname, ndirfd, name, 0);
 964    if (ret < 0) {
 965        goto out_close;
 966    }
 967
 968    /* now link the virtfs_metadata files */
 969    if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
 970        int omap_dirfd, nmap_dirfd;
 971
 972        ret = mkdirat(ndirfd, VIRTFS_META_DIR, 0700);
 973        if (ret < 0 && errno != EEXIST) {
 974            goto err_undo_link;
 975        }
 976
 977        omap_dirfd = openat_dir(odirfd, VIRTFS_META_DIR);
 978        if (omap_dirfd == -1) {
 979            goto err;
 980        }
 981
 982        nmap_dirfd = openat_dir(ndirfd, VIRTFS_META_DIR);
 983        if (nmap_dirfd == -1) {
 984            close_preserve_errno(omap_dirfd);
 985            goto err;
 986        }
 987
 988        ret = linkat(omap_dirfd, oname, nmap_dirfd, name, 0);
 989        close_preserve_errno(nmap_dirfd);
 990        close_preserve_errno(omap_dirfd);
 991        if (ret < 0 && errno != ENOENT) {
 992            goto err_undo_link;
 993        }
 994
 995        ret = 0;
 996    }
 997    goto out_close;
 998
 999err:
1000    ret = -1;
1001err_undo_link:
1002    unlinkat_preserve_errno(ndirfd, name, 0);
1003out_close:
1004    close_preserve_errno(ndirfd);
1005    close_preserve_errno(odirfd);
1006out:
1007    g_free(oname);
1008    g_free(odirpath);
1009    return ret;
1010}
1011
1012static int local_truncate(FsContext *ctx, V9fsPath *fs_path, off_t size)
1013{
1014    int fd, ret;
1015
1016    fd = local_open_nofollow(ctx, fs_path->data, O_WRONLY, 0);
1017    if (fd == -1) {
1018        return -1;
1019    }
1020    ret = ftruncate(fd, size);
1021    close_preserve_errno(fd);
1022    return ret;
1023}
1024
1025static int local_chown(FsContext *fs_ctx, V9fsPath *fs_path, FsCred *credp)
1026{
1027    char *dirpath = g_path_get_dirname(fs_path->data);
1028    char *name = g_path_get_basename(fs_path->data);
1029    int ret = -1;
1030    int dirfd;
1031
1032    dirfd = local_opendir_nofollow(fs_ctx, dirpath);
1033    if (dirfd == -1) {
1034        goto out;
1035    }
1036
1037    if ((credp->fc_uid == -1 && credp->fc_gid == -1) ||
1038        (fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) ||
1039        (fs_ctx->export_flags & V9FS_SM_NONE)) {
1040        ret = fchownat(dirfd, name, credp->fc_uid, credp->fc_gid,
1041                       AT_SYMLINK_NOFOLLOW);
1042    } else if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
1043        ret = local_set_xattrat(dirfd, name, credp);
1044    } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
1045        ret = local_set_mapped_file_attrat(dirfd, name, credp);
1046    }
1047
1048    close_preserve_errno(dirfd);
1049out:
1050    g_free(name);
1051    g_free(dirpath);
1052    return ret;
1053}
1054
1055static int local_utimensat(FsContext *s, V9fsPath *fs_path,
1056                           const struct timespec *buf)
1057{
1058    char *dirpath = g_path_get_dirname(fs_path->data);
1059    char *name = g_path_get_basename(fs_path->data);
1060    int dirfd, ret = -1;
1061
1062    dirfd = local_opendir_nofollow(s, dirpath);
1063    if (dirfd == -1) {
1064        goto out;
1065    }
1066
1067    ret = utimensat(dirfd, name, buf, AT_SYMLINK_NOFOLLOW);
1068    close_preserve_errno(dirfd);
1069out:
1070    g_free(dirpath);
1071    g_free(name);
1072    return ret;
1073}
1074
1075static int local_unlinkat_common(FsContext *ctx, int dirfd, const char *name,
1076                                 int flags)
1077{
1078    int ret = -1;
1079
1080    if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
1081        int map_dirfd;
1082
1083        /* We need to remove the metadata as well:
1084         * - the metadata directory if we're removing a directory
1085         * - the metadata file in the parent's metadata directory
1086         *
1087         * If any of these are missing (ie, ENOENT) then we're probably
1088         * trying to remove something that wasn't created in mapped-file
1089         * mode. We just ignore the error.
1090         */
1091        if (flags == AT_REMOVEDIR) {
1092            int fd;
1093
1094            fd = openat_dir(dirfd, name);
1095            if (fd == -1) {
1096                goto err_out;
1097            }
1098            ret = unlinkat(fd, VIRTFS_META_DIR, AT_REMOVEDIR);
1099            close_preserve_errno(fd);
1100            if (ret < 0 && errno != ENOENT) {
1101                goto err_out;
1102            }
1103        }
1104        map_dirfd = openat_dir(dirfd, VIRTFS_META_DIR);
1105        if (map_dirfd != -1) {
1106            ret = unlinkat(map_dirfd, name, 0);
1107            close_preserve_errno(map_dirfd);
1108            if (ret < 0 && errno != ENOENT) {
1109                goto err_out;
1110            }
1111        } else if (errno != ENOENT) {
1112            goto err_out;
1113        }
1114    }
1115
1116    ret = unlinkat(dirfd, name, flags);
1117err_out:
1118    return ret;
1119}
1120
1121static int local_remove(FsContext *ctx, const char *path)
1122{
1123    struct stat stbuf;
1124    char *dirpath = g_path_get_dirname(path);
1125    char *name = g_path_get_basename(path);
1126    int flags = 0;
1127    int dirfd;
1128    int err = -1;
1129
1130    dirfd = local_opendir_nofollow(ctx, dirpath);
1131    if (dirfd == -1) {
1132        goto out;
1133    }
1134
1135    if (fstatat(dirfd, name, &stbuf, AT_SYMLINK_NOFOLLOW) < 0) {
1136        goto err_out;
1137    }
1138
1139    if (S_ISDIR(stbuf.st_mode)) {
1140        flags |= AT_REMOVEDIR;
1141    }
1142
1143    err = local_unlinkat_common(ctx, dirfd, name, flags);
1144err_out:
1145    close_preserve_errno(dirfd);
1146out:
1147    g_free(name);
1148    g_free(dirpath);
1149    return err;
1150}
1151
1152static int local_fsync(FsContext *ctx, int fid_type,
1153                       V9fsFidOpenState *fs, int datasync)
1154{
1155    int fd;
1156
1157    if (fid_type == P9_FID_DIR) {
1158        fd = dirfd(fs->dir.stream);
1159    } else {
1160        fd = fs->fd;
1161    }
1162
1163    if (datasync) {
1164        return qemu_fdatasync(fd);
1165    } else {
1166        return fsync(fd);
1167    }
1168}
1169
1170static int local_statfs(FsContext *s, V9fsPath *fs_path, struct statfs *stbuf)
1171{
1172    int fd, ret;
1173
1174    fd = local_open_nofollow(s, fs_path->data, O_RDONLY, 0);
1175    if (fd == -1) {
1176        return -1;
1177    }
1178    ret = fstatfs(fd, stbuf);
1179    close_preserve_errno(fd);
1180    return ret;
1181}
1182
1183static ssize_t local_lgetxattr(FsContext *ctx, V9fsPath *fs_path,
1184                               const char *name, void *value, size_t size)
1185{
1186    char *path = fs_path->data;
1187
1188    return v9fs_get_xattr(ctx, path, name, value, size);
1189}
1190
1191static ssize_t local_llistxattr(FsContext *ctx, V9fsPath *fs_path,
1192                                void *value, size_t size)
1193{
1194    char *path = fs_path->data;
1195
1196    return v9fs_list_xattr(ctx, path, value, size);
1197}
1198
1199static int local_lsetxattr(FsContext *ctx, V9fsPath *fs_path, const char *name,
1200                           void *value, size_t size, int flags)
1201{
1202    char *path = fs_path->data;
1203
1204    return v9fs_set_xattr(ctx, path, name, value, size, flags);
1205}
1206
1207static int local_lremovexattr(FsContext *ctx, V9fsPath *fs_path,
1208                              const char *name)
1209{
1210    char *path = fs_path->data;
1211
1212    return v9fs_remove_xattr(ctx, path, name);
1213}
1214
1215static int local_name_to_path(FsContext *ctx, V9fsPath *dir_path,
1216                              const char *name, V9fsPath *target)
1217{
1218    if (ctx->export_flags & V9FS_SM_MAPPED_FILE &&
1219        local_is_mapped_file_metadata(ctx, name)) {
1220        errno = EINVAL;
1221        return -1;
1222    }
1223
1224    if (dir_path) {
1225        if (!strcmp(name, ".")) {
1226            /* "." relative to "foo/bar" is "foo/bar" */
1227            v9fs_path_copy(target, dir_path);
1228        } else if (!strcmp(name, "..")) {
1229            if (!strcmp(dir_path->data, ".")) {
1230                /* ".." relative to the root is "." */
1231                v9fs_path_sprintf(target, ".");
1232            } else {
1233                char *tmp = g_path_get_dirname(dir_path->data);
1234                /* Symbolic links are resolved by the client. We can assume
1235                 * that ".." relative to "foo/bar" is equivalent to "foo"
1236                 */
1237                v9fs_path_sprintf(target, "%s", tmp);
1238                g_free(tmp);
1239            }
1240        } else {
1241            assert(!strchr(name, '/'));
1242            v9fs_path_sprintf(target, "%s/%s", dir_path->data, name);
1243        }
1244    } else if (!strcmp(name, "/") || !strcmp(name, ".") ||
1245               !strcmp(name, "..")) {
1246            /* This is the root fid */
1247        v9fs_path_sprintf(target, ".");
1248    } else {
1249        assert(!strchr(name, '/'));
1250        v9fs_path_sprintf(target, "./%s", name);
1251    }
1252    return 0;
1253}
1254
1255static int local_renameat(FsContext *ctx, V9fsPath *olddir,
1256                          const char *old_name, V9fsPath *newdir,
1257                          const char *new_name)
1258{
1259    int ret;
1260    int odirfd, ndirfd;
1261
1262    if (ctx->export_flags & V9FS_SM_MAPPED_FILE &&
1263        (local_is_mapped_file_metadata(ctx, old_name) ||
1264         local_is_mapped_file_metadata(ctx, new_name))) {
1265        errno = EINVAL;
1266        return -1;
1267    }
1268
1269    odirfd = local_opendir_nofollow(ctx, olddir->data);
1270    if (odirfd == -1) {
1271        return -1;
1272    }
1273
1274    ndirfd = local_opendir_nofollow(ctx, newdir->data);
1275    if (ndirfd == -1) {
1276        close_preserve_errno(odirfd);
1277        return -1;
1278    }
1279
1280    ret = renameat(odirfd, old_name, ndirfd, new_name);
1281    if (ret < 0) {
1282        goto out;
1283    }
1284
1285    if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
1286        int omap_dirfd, nmap_dirfd;
1287
1288        ret = mkdirat(ndirfd, VIRTFS_META_DIR, 0700);
1289        if (ret < 0 && errno != EEXIST) {
1290            goto err_undo_rename;
1291        }
1292
1293        omap_dirfd = openat_dir(odirfd, VIRTFS_META_DIR);
1294        if (omap_dirfd == -1) {
1295            goto err;
1296        }
1297
1298        nmap_dirfd = openat_dir(ndirfd, VIRTFS_META_DIR);
1299        if (nmap_dirfd == -1) {
1300            close_preserve_errno(omap_dirfd);
1301            goto err;
1302        }
1303
1304        /* rename the .virtfs_metadata files */
1305        ret = renameat(omap_dirfd, old_name, nmap_dirfd, new_name);
1306        close_preserve_errno(nmap_dirfd);
1307        close_preserve_errno(omap_dirfd);
1308        if (ret < 0 && errno != ENOENT) {
1309            goto err_undo_rename;
1310        }
1311
1312        ret = 0;
1313    }
1314    goto out;
1315
1316err:
1317    ret = -1;
1318err_undo_rename:
1319    renameat_preserve_errno(ndirfd, new_name, odirfd, old_name);
1320out:
1321    close_preserve_errno(ndirfd);
1322    close_preserve_errno(odirfd);
1323    return ret;
1324}
1325
1326static void v9fs_path_init_dirname(V9fsPath *path, const char *str)
1327{
1328    path->data = g_path_get_dirname(str);
1329    path->size = strlen(path->data) + 1;
1330}
1331
1332static int local_rename(FsContext *ctx, const char *oldpath,
1333                        const char *newpath)
1334{
1335    int err;
1336    char *oname = g_path_get_basename(oldpath);
1337    char *nname = g_path_get_basename(newpath);
1338    V9fsPath olddir, newdir;
1339
1340    v9fs_path_init_dirname(&olddir, oldpath);
1341    v9fs_path_init_dirname(&newdir, newpath);
1342
1343    err = local_renameat(ctx, &olddir, oname, &newdir, nname);
1344
1345    v9fs_path_free(&newdir);
1346    v9fs_path_free(&olddir);
1347    g_free(nname);
1348    g_free(oname);
1349
1350    return err;
1351}
1352
1353static int local_unlinkat(FsContext *ctx, V9fsPath *dir,
1354                          const char *name, int flags)
1355{
1356    int ret;
1357    int dirfd;
1358
1359    if (ctx->export_flags & V9FS_SM_MAPPED_FILE &&
1360        local_is_mapped_file_metadata(ctx, name)) {
1361        errno = EINVAL;
1362        return -1;
1363    }
1364
1365    dirfd = local_opendir_nofollow(ctx, dir->data);
1366    if (dirfd == -1) {
1367        return -1;
1368    }
1369
1370    ret = local_unlinkat_common(ctx, dirfd, name, flags);
1371    close_preserve_errno(dirfd);
1372    return ret;
1373}
1374
1375static int local_ioc_getversion(FsContext *ctx, V9fsPath *path,
1376                                mode_t st_mode, uint64_t *st_gen)
1377{
1378#ifdef FS_IOC_GETVERSION
1379    int err;
1380    V9fsFidOpenState fid_open;
1381
1382    /*
1383     * Do not try to open special files like device nodes, fifos etc
1384     * We can get fd for regular files and directories only
1385     */
1386    if (!S_ISREG(st_mode) && !S_ISDIR(st_mode)) {
1387        errno = ENOTTY;
1388        return -1;
1389    }
1390    err = local_open(ctx, path, O_RDONLY, &fid_open);
1391    if (err < 0) {
1392        return err;
1393    }
1394    err = ioctl(fid_open.fd, FS_IOC_GETVERSION, st_gen);
1395    local_close(ctx, &fid_open);
1396    return err;
1397#else
1398    errno = ENOTTY;
1399    return -1;
1400#endif
1401}
1402
1403static int local_init(FsContext *ctx)
1404{
1405    struct statfs stbuf;
1406    LocalData *data = g_malloc(sizeof(*data));
1407
1408    data->mountfd = open(ctx->fs_root, O_DIRECTORY | O_RDONLY);
1409    if (data->mountfd == -1) {
1410        goto err;
1411    }
1412
1413#ifdef FS_IOC_GETVERSION
1414    /*
1415     * use ioc_getversion only if the ioctl is definied
1416     */
1417    if (fstatfs(data->mountfd, &stbuf) < 0) {
1418        close_preserve_errno(data->mountfd);
1419        goto err;
1420    }
1421    switch (stbuf.f_type) {
1422    case EXT2_SUPER_MAGIC:
1423    case BTRFS_SUPER_MAGIC:
1424    case REISERFS_SUPER_MAGIC:
1425    case XFS_SUPER_MAGIC:
1426        ctx->exops.get_st_gen = local_ioc_getversion;
1427        break;
1428    }
1429#endif
1430
1431    if (ctx->export_flags & V9FS_SM_PASSTHROUGH) {
1432        ctx->xops = passthrough_xattr_ops;
1433    } else if (ctx->export_flags & V9FS_SM_MAPPED) {
1434        ctx->xops = mapped_xattr_ops;
1435    } else if (ctx->export_flags & V9FS_SM_NONE) {
1436        ctx->xops = none_xattr_ops;
1437    } else if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
1438        /*
1439         * xattr operation for mapped-file and passthrough
1440         * remain same.
1441         */
1442        ctx->xops = passthrough_xattr_ops;
1443    }
1444    ctx->export_flags |= V9FS_PATHNAME_FSCONTEXT;
1445
1446    ctx->private = data;
1447    return 0;
1448
1449err:
1450    g_free(data);
1451    return -1;
1452}
1453
1454static void local_cleanup(FsContext *ctx)
1455{
1456    LocalData *data = ctx->private;
1457
1458    close(data->mountfd);
1459    g_free(data);
1460}
1461
1462static int local_parse_opts(QemuOpts *opts, struct FsDriverEntry *fse)
1463{
1464    const char *sec_model = qemu_opt_get(opts, "security_model");
1465    const char *path = qemu_opt_get(opts, "path");
1466    Error *err = NULL;
1467
1468    if (!sec_model) {
1469        error_report("Security model not specified, local fs needs security model");
1470        error_printf("valid options are:"
1471                     "\tsecurity_model=[passthrough|mapped-xattr|mapped-file|none]\n");
1472        return -1;
1473    }
1474
1475    if (!strcmp(sec_model, "passthrough")) {
1476        fse->export_flags |= V9FS_SM_PASSTHROUGH;
1477    } else if (!strcmp(sec_model, "mapped") ||
1478               !strcmp(sec_model, "mapped-xattr")) {
1479        fse->export_flags |= V9FS_SM_MAPPED;
1480    } else if (!strcmp(sec_model, "none")) {
1481        fse->export_flags |= V9FS_SM_NONE;
1482    } else if (!strcmp(sec_model, "mapped-file")) {
1483        fse->export_flags |= V9FS_SM_MAPPED_FILE;
1484    } else {
1485        error_report("Invalid security model %s specified", sec_model);
1486        error_printf("valid options are:"
1487                     "\t[passthrough|mapped-xattr|mapped-file|none]\n");
1488        return -1;
1489    }
1490
1491    if (!path) {
1492        error_report("fsdev: No path specified");
1493        return -1;
1494    }
1495
1496    fsdev_throttle_parse_opts(opts, &fse->fst, &err);
1497    if (err) {
1498        error_reportf_err(err, "Throttle configuration is not valid: ");
1499        return -1;
1500    }
1501
1502    if (fse->export_flags & V9FS_SM_MAPPED ||
1503        fse->export_flags & V9FS_SM_MAPPED_FILE) {
1504        fse->fmode =
1505            qemu_opt_get_number(opts, "fmode", SM_LOCAL_MODE_BITS) & 0777;
1506        fse->dmode =
1507            qemu_opt_get_number(opts, "dmode", SM_LOCAL_DIR_MODE_BITS) & 0777;
1508    } else {
1509        if (qemu_opt_find(opts, "fmode")) {
1510            error_report("fmode is only valid for mapped 9p modes");
1511            return -1;
1512        }
1513        if (qemu_opt_find(opts, "dmode")) {
1514            error_report("dmode is only valid for mapped 9p modes");
1515            return -1;
1516        }
1517    }
1518
1519    fse->path = g_strdup(path);
1520
1521    return 0;
1522}
1523
1524FileOperations local_ops = {
1525    .parse_opts = local_parse_opts,
1526    .init  = local_init,
1527    .cleanup = local_cleanup,
1528    .lstat = local_lstat,
1529    .readlink = local_readlink,
1530    .close = local_close,
1531    .closedir = local_closedir,
1532    .open = local_open,
1533    .opendir = local_opendir,
1534    .rewinddir = local_rewinddir,
1535    .telldir = local_telldir,
1536    .readdir = local_readdir,
1537    .seekdir = local_seekdir,
1538    .preadv = local_preadv,
1539    .pwritev = local_pwritev,
1540    .chmod = local_chmod,
1541    .mknod = local_mknod,
1542    .mkdir = local_mkdir,
1543    .fstat = local_fstat,
1544    .open2 = local_open2,
1545    .symlink = local_symlink,
1546    .link = local_link,
1547    .truncate = local_truncate,
1548    .rename = local_rename,
1549    .chown = local_chown,
1550    .utimensat = local_utimensat,
1551    .remove = local_remove,
1552    .fsync = local_fsync,
1553    .statfs = local_statfs,
1554    .lgetxattr = local_lgetxattr,
1555    .llistxattr = local_llistxattr,
1556    .lsetxattr = local_lsetxattr,
1557    .lremovexattr = local_lremovexattr,
1558    .name_to_path = local_name_to_path,
1559    .renameat  = local_renameat,
1560    .unlinkat = local_unlinkat,
1561};
1562