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