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