Commit message: 9pfs: adjust the order of resource cleanup in device unrealize

Unrealize should undo things that were set during realize in
reverse order. So should do in the error path in realize.

Signed-off-by: Li Qiang <liq3ea@gmail.com>
Reviewed-by: Greg Kurz <groug@kaod.org>
Signed-off-by: Greg Kurz <groug@kaod.org>
CVE: CVE-2016-9913
CWE: CWE-400
Vulnerability description: Memory leak in the v9fs_device_unrealize_common function in hw/9pfs/9p.c in QEMU (aka Quick Emulator) allows local privileged guest OS users to cause a denial of service (host memory consumption and possibly QEMU process crash) via vectors involving the order of resource cleanup.

Project: qemu
File: 9p.c

static int coroutine_fn v9fs_do_readdir_with_stat(V9fsPDU *pdu,
                                                  V9fsFidState *fidp,
                                                  uint32_t max_count)
{
    V9fsPath path;
    V9fsStat v9stat;
    int len, err = 0;
    int32_t count = 0;
    struct stat stbuf;
    off_t saved_dir_pos;
    struct dirent *dent;

    /* save the directory position */
    saved_dir_pos = v9fs_co_telldir(pdu, fidp);
    if (saved_dir_pos < 0) {
        return saved_dir_pos;
    }

    while (1) {
        v9fs_path_init(&path);

        v9fs_readdir_lock(&fidp->fs.dir);

        err = v9fs_co_readdir(pdu, fidp, &dent);
        if (err || !dent) {
            break;
        }
        err = v9fs_co_name_to_path(pdu, &fidp->path, dent->d_name, &path);
        if (err < 0) {
            break;
        }
        err = v9fs_co_lstat(pdu, &path, &stbuf);
        if (err < 0) {
            break;
        }
        err = stat_to_v9stat(pdu, &path, &stbuf, &v9stat);
        if (err < 0) {
            break;
        }
        /* 11 = 7 + 4 (7 = start offset, 4 = space for storing count) */
        len = pdu_marshal(pdu, 11 + count, "S", &v9stat);

        v9fs_readdir_unlock(&fidp->fs.dir);

        if ((len != (v9stat.size + 2)) || ((count + len) > max_count)) {
            /* Ran out of buffer. Set dir back to old position and return */
            v9fs_co_seekdir(pdu, fidp, saved_dir_pos);
            v9fs_stat_free(&v9stat);
            v9fs_path_free(&path);
            return count;
        }
        count += len;
        v9fs_stat_free(&v9stat);
        v9fs_path_free(&path);
        saved_dir_pos = dent->d_off;
    }

    v9fs_readdir_unlock(&fidp->fs.dir);

    v9fs_path_free(&path);
    if (err < 0) {
        return err;
    }
    return count;
}

Project: qemu
File: 9p.c

static int v9fs_fill_statfs(V9fsState *s, V9fsPDU *pdu, struct statfs *stbuf)
{
    uint32_t f_type;
    uint32_t f_bsize;
    uint64_t f_blocks;
    uint64_t f_bfree;
    uint64_t f_bavail;
    uint64_t f_files;
    uint64_t f_ffree;
    uint64_t fsid_val;
    uint32_t f_namelen;
    size_t offset = 7;
    int32_t bsize_factor;

    /*
     * compute bsize factor based on host file system block size
     * and client msize
     */
    bsize_factor = (s->msize - P9_IOHDRSZ)/stbuf->f_bsize;
    if (!bsize_factor) {
        bsize_factor = 1;
    }
    f_type  = stbuf->f_type;
    f_bsize = stbuf->f_bsize;
    f_bsize *= bsize_factor;
    /*
     * f_bsize is adjusted(multiplied) by bsize factor, so we need to
     * adjust(divide) the number of blocks, free blocks and available
     * blocks by bsize factor
     */
    f_blocks = stbuf->f_blocks/bsize_factor;
    f_bfree  = stbuf->f_bfree/bsize_factor;
    f_bavail = stbuf->f_bavail/bsize_factor;
    f_files  = stbuf->f_files;
    f_ffree  = stbuf->f_ffree;
    fsid_val = (unsigned int) stbuf->f_fsid.__val[0] |
               (unsigned long long)stbuf->f_fsid.__val[1] << 32;
    f_namelen = stbuf->f_namelen;

    return pdu_marshal(pdu, offset, "ddqqqqqqd",
                       f_type, f_bsize, f_blocks, f_bfree,
                       f_bavail, f_files, f_ffree,
                       fsid_val, f_namelen);
}

Project: qemu
File: 9p.c

static void coroutine_fn v9fs_fix_fid_paths(V9fsPDU *pdu, V9fsPath *olddir,
                                            V9fsString *old_name,
                                            V9fsPath *newdir,
                                            V9fsString *new_name)
{
    V9fsFidState *tfidp;
    V9fsPath oldpath, newpath;
    V9fsState *s = pdu->s;


    v9fs_path_init(&oldpath);
    v9fs_path_init(&newpath);
    v9fs_co_name_to_path(pdu, olddir, old_name->data, &oldpath);
    v9fs_co_name_to_path(pdu, newdir, new_name->data, &newpath);

    /*
     * Fixup fid's pointing to the old name to
     * start pointing to the new name
     */
    for (tfidp = s->fid_list; tfidp; tfidp = tfidp->next) {
        if (v9fs_path_is_ancestor(&oldpath, &tfidp->path)) {
            /* replace the name */
            v9fs_fix_path(&tfidp->path, &newpath, strlen(oldpath.data));
        }
    }
    v9fs_path_free(&oldpath);
    v9fs_path_free(&newpath);
}

Project: qemu
File: 9p.c

static void v9fs_flush(void *opaque)
{
    ssize_t err;
    int16_t tag;
    size_t offset = 7;
    V9fsPDU *cancel_pdu;
    V9fsPDU *pdu = opaque;
    V9fsState *s = pdu->s;

    err = pdu_unmarshal(pdu, offset, "w", &tag);
    if (err < 0) {
        pdu_complete(pdu, err);
        return;
    }
    trace_v9fs_flush(pdu->tag, pdu->id, tag);

    QLIST_FOREACH(cancel_pdu, &s->active_list, next) {
        if (cancel_pdu->tag == tag) {
            break;
        }
    }
    if (cancel_pdu) {
        cancel_pdu->cancelled = 1;
        /*
         * Wait for pdu to complete.
         */
        qemu_co_queue_wait(&cancel_pdu->complete);
        cancel_pdu->cancelled = 0;
        pdu_free(cancel_pdu);
    }
    pdu_complete(pdu, 7);
}

Project: qemu
File: 9p.c

static void coroutine_fn v9fs_fs_ro(void *opaque)
{
    V9fsPDU *pdu = opaque;
    pdu_complete(pdu, -EROFS);
}

Project: qemu
File: 9p.c

static void v9fs_fsync(void *opaque)
{
    int err;
    int32_t fid;
    int datasync;
    size_t offset = 7;
    V9fsFidState *fidp;
    V9fsPDU *pdu = opaque;

    err = pdu_unmarshal(pdu, offset, "dd", &fid, &datasync);
    if (err < 0) {
        goto out_nofid;
    }
    trace_v9fs_fsync(pdu->tag, pdu->id, fid, datasync);

    fidp = get_fid(pdu, fid);
    if (fidp == NULL) {
        err = -ENOENT;
        goto out_nofid;
    }
    err = v9fs_co_fsync(pdu, fidp, datasync);
    if (!err) {
        err = offset;
    }
    put_fid(pdu, fidp);
out_nofid:
    pdu_complete(pdu, err);
}

Project: qemu
File: 9p.c

static void coroutine_fn v9fs_getlock(void *opaque)
{
    size_t offset = 7;
    struct stat stbuf;
    V9fsFidState *fidp;
    V9fsGetlock glock;
    int32_t fid, err = 0;
    V9fsPDU *pdu = opaque;

    v9fs_string_init(&glock.client_id);
    err = pdu_unmarshal(pdu, offset, "dbqqds", &fid, &glock.type,
                        &glock.start, &glock.length, &glock.proc_id,
                        &glock.client_id);
    if (err < 0) {
        goto out_nofid;
    }
    trace_v9fs_getlock(pdu->tag, pdu->id, fid,
                       glock.type, glock.start, glock.length);

    fidp = get_fid(pdu, fid);
    if (fidp == NULL) {
        err = -ENOENT;
        goto out_nofid;
    }
    err = v9fs_co_fstat(pdu, fidp, &stbuf);
    if (err < 0) {
        goto out;
    }
    glock.type = P9_LOCK_TYPE_UNLCK;
    err = pdu_marshal(pdu, offset, "bqqds", glock.type,
                          glock.start, glock.length, glock.proc_id,
                          &glock.client_id);
    if (err < 0) {
        goto out;
    }
    err += offset;
    trace_v9fs_getlock_return(pdu->tag, pdu->id, glock.type, glock.start,
                              glock.length, glock.proc_id);
out:
    put_fid(pdu, fidp);
out_nofid:
    pdu_complete(pdu, err);
    v9fs_string_free(&glock.client_id);
}

Project: qemu
File: 9p.c

static void v9fs_init_qiov_from_pdu(QEMUIOVector *qiov, V9fsPDU *pdu,
                                    size_t skip, size_t size,
                                    bool is_write)
{
    QEMUIOVector elem;
    struct iovec *iov;
    unsigned int niov;

    virtio_init_iov_from_pdu(pdu, &iov, &niov, is_write);

    qemu_iovec_init_external(&elem, iov, niov);
    qemu_iovec_init(qiov, niov);
    qemu_iovec_concat(qiov, &elem, skip, size);
}

Project: qemu
File: 9p.c

static void coroutine_fn v9fs_lcreate(void *opaque)
{
    int32_t dfid, flags, mode;
    gid_t gid;
    ssize_t err = 0;
    ssize_t offset = 7;
    V9fsString name;
    V9fsFidState *fidp;
    struct stat stbuf;
    V9fsQID qid;
    int32_t iounit;
    V9fsPDU *pdu = opaque;

    v9fs_string_init(&name);
    err = pdu_unmarshal(pdu, offset, "dsddd", &dfid,
                        &name, &flags, &mode, &gid);
    if (err < 0) {
        goto out_nofid;
    }
    trace_v9fs_lcreate(pdu->tag, pdu->id, dfid, flags, mode, gid);

    if (name_is_illegal(name.data)) {
        err = -ENOENT;
        goto out_nofid;
    }

    if (!strcmp(".", name.data) || !strcmp("..", name.data)) {
        err = -EEXIST;
        goto out_nofid;
    }

    fidp = get_fid(pdu, dfid);
    if (fidp == NULL) {
        err = -ENOENT;
        goto out_nofid;
    }

    flags = get_dotl_openflags(pdu->s, flags);
    err = v9fs_co_open2(pdu, fidp, &name, gid,
                        flags | O_CREAT, mode, &stbuf);
    if (err < 0) {
        goto out;
    }
    fidp->fid_type = P9_FID_FILE;
    fidp->open_flags = flags;
    if (flags & O_EXCL) {
        /*
         * We let the host file system do O_EXCL check
         * We should not reclaim such fd
         */
        fidp->flags |= FID_NON_RECLAIMABLE;
    }
    iounit =  get_iounit(pdu, &fidp->path);
    stat_to_qid(&stbuf, &qid);
    err = pdu_marshal(pdu, offset, "Qd", &qid, iounit);
    if (err < 0) {
        goto out;
    }
    err += offset;
    trace_v9fs_lcreate_return(pdu->tag, pdu->id,
                              qid.type, qid.version, qid.path, iounit);
out:
    put_fid(pdu, fidp);
out_nofid:
    pdu_complete(pdu, err);
    v9fs_string_free(&name);
}

Project: qemu
File: 9p.c

static void coroutine_fn v9fs_lock(void *opaque)
{
    int8_t status;
    V9fsFlock flock;
    size_t offset = 7;
    struct stat stbuf;
    V9fsFidState *fidp;
    int32_t fid, err = 0;
    V9fsPDU *pdu = opaque;

    status = P9_LOCK_ERROR;
    v9fs_string_init(&flock.client_id);
    err = pdu_unmarshal(pdu, offset, "dbdqqds", &fid, &flock.type,
                        &flock.flags, &flock.start, &flock.length,
                        &flock.proc_id, &flock.client_id);
    if (err < 0) {
        goto out_nofid;
    }
    trace_v9fs_lock(pdu->tag, pdu->id, fid,
                    flock.type, flock.start, flock.length);


    /* We support only block flag now (that too ignored currently) */
    if (flock.flags & ~P9_LOCK_FLAGS_BLOCK) {
        err = -EINVAL;
        goto out_nofid;
    }
    fidp = get_fid(pdu, fid);
    if (fidp == NULL) {
        err = -ENOENT;
        goto out_nofid;
    }
    err = v9fs_co_fstat(pdu, fidp, &stbuf);
    if (err < 0) {
        goto out;
    }
    status = P9_LOCK_SUCCESS;
out:
    put_fid(pdu, fidp);
out_nofid:
    err = pdu_marshal(pdu, offset, "b", status);
    if (err > 0) {
        err += offset;
    }
    trace_v9fs_lock_return(pdu->tag, pdu->id, status);
    pdu_complete(pdu, err);
    v9fs_string_free(&flock.client_id);
}

Project: qemu
File: 9p.c

static int coroutine_fn v9fs_mark_fids_unreclaim(V9fsPDU *pdu, V9fsPath *path)
{
    int err;
    V9fsState *s = pdu->s;
    V9fsFidState *fidp, head_fid;

    head_fid.next = s->fid_list;
    for (fidp = s->fid_list; fidp; fidp = fidp->next) {
        if (fidp->path.size != path->size) {
            continue;
        }
        if (!memcmp(fidp->path.data, path->data, path->size)) {
            /* Mark the fid non reclaimable. */
            fidp->flags |= FID_NON_RECLAIMABLE;

            /* reopen the file/dir if already closed */
            err = v9fs_reopen_fid(pdu, fidp);
            if (err < 0) {
                return -1;
            }
            /*
             * Go back to head of fid list because
             * the list could have got updated when
             * switched to the worker thread
             */
            if (err == 0) {
                fidp = &head_fid;
            }
        }
    }
    return 0;
}

Project: qemu
File: 9p.c

static void coroutine_fn v9fs_mkdir(void *opaque)
{
    V9fsPDU *pdu = opaque;
    size_t offset = 7;
    int32_t fid;
    struct stat stbuf;
    V9fsQID qid;
    V9fsString name;
    V9fsFidState *fidp;
    gid_t gid;
    int mode;
    int err = 0;

    v9fs_string_init(&name);
    err = pdu_unmarshal(pdu, offset, "dsdd", &fid, &name, &mode, &gid);
    if (err < 0) {
        goto out_nofid;
    }
    trace_v9fs_mkdir(pdu->tag, pdu->id, fid, name.data, mode, gid);

    if (name_is_illegal(name.data)) {
        err = -ENOENT;
        goto out_nofid;
    }

    if (!strcmp(".", name.data) || !strcmp("..", name.data)) {
        err = -EEXIST;
        goto out_nofid;
    }

    fidp = get_fid(pdu, fid);
    if (fidp == NULL) {
        err = -ENOENT;
        goto out_nofid;
    }
    err = v9fs_co_mkdir(pdu, fidp, &name, mode, fidp->uid, gid, &stbuf);
    if (err < 0) {
        goto out;
    }
    stat_to_qid(&stbuf, &qid);
    err = pdu_marshal(pdu, offset, "Q", &qid);
    if (err < 0) {
        goto out;
    }
    err += offset;
    trace_v9fs_mkdir_return(pdu->tag, pdu->id,
                            qid.type, qid.version, qid.path, err);
out:
    put_fid(pdu, fidp);
out_nofid:
    pdu_complete(pdu, err);
    v9fs_string_free(&name);
}

Project: qemu
File: 9p.c

static void coroutine_fn v9fs_mknod(void *opaque)
{

    int mode;
    gid_t gid;
    int32_t fid;
    V9fsQID qid;
    int err = 0;
    int major, minor;
    size_t offset = 7;
    V9fsString name;
    struct stat stbuf;
    V9fsFidState *fidp;
    V9fsPDU *pdu = opaque;

    v9fs_string_init(&name);
    err = pdu_unmarshal(pdu, offset, "dsdddd", &fid, &name, &mode,
                        &major, &minor, &gid);
    if (err < 0) {
        goto out_nofid;
    }
    trace_v9fs_mknod(pdu->tag, pdu->id, fid, mode, major, minor);

    if (name_is_illegal(name.data)) {
        err = -ENOENT;
        goto out_nofid;
    }

    if (!strcmp(".", name.data) || !strcmp("..", name.data)) {
        err = -EEXIST;
        goto out_nofid;
    }

    fidp = get_fid(pdu, fid);
    if (fidp == NULL) {
        err = -ENOENT;
        goto out_nofid;
    }
    err = v9fs_co_mknod(pdu, fidp, &name, fidp->uid, gid,
                        makedev(major, minor), mode, &stbuf);
    if (err < 0) {
        goto out;
    }
    stat_to_qid(&stbuf, &qid);
    err = pdu_marshal(pdu, offset, "Q", &qid);
    if (err < 0) {
        goto out;
    }
    err += offset;
    trace_v9fs_mknod_return(pdu->tag, pdu->id,
                            qid.type, qid.version, qid.path);
out:
    put_fid(pdu, fidp);
out_nofid:
    pdu_complete(pdu, err);
    v9fs_string_free(&name);
}

Project: qemu
File: 9p.c

static void coroutine_fn v9fs_op_not_supp(void *opaque)
{
    V9fsPDU *pdu = opaque;
    pdu_complete(pdu, -EOPNOTSUPP);
}

Project: qemu
File: 9p.c

void v9fs_path_copy(V9fsPath *lhs, V9fsPath *rhs)
{
    v9fs_path_free(lhs);
    lhs->data = g_malloc(rhs->size);
    memcpy(lhs->data, rhs->data, rhs->size);
    lhs->size = rhs->size;
}

Project: qemu
File: 9p.c

static void coroutine_fn v9fs_readlink(void *opaque)
{
    V9fsPDU *pdu = opaque;
    size_t offset = 7;
    V9fsString target;
    int32_t fid;
    int err = 0;
    V9fsFidState *fidp;

    err = pdu_unmarshal(pdu, offset, "d", &fid);
    if (err < 0) {
        goto out_nofid;
    }
    trace_v9fs_readlink(pdu->tag, pdu->id, fid);
    fidp = get_fid(pdu, fid);
    if (fidp == NULL) {
        err = -ENOENT;
        goto out_nofid;
    }

    v9fs_string_init(&target);
    err = v9fs_co_readlink(pdu, &fidp->path, &target);
    if (err < 0) {
        goto out;
    }
    err = pdu_marshal(pdu, offset, "s", &target);
    if (err < 0) {
        v9fs_string_free(&target);
        goto out;
    }
    err += offset;
    trace_v9fs_readlink_return(pdu->tag, pdu->id, target.data);
    v9fs_string_free(&target);
out:
    put_fid(pdu, fidp);
out_nofid:
    pdu_complete(pdu, err);
}

Project: qemu
File: 9p.c

void v9fs_reset(V9fsState *s)
{
    VirtfsCoResetData data = { .pdu = { .s = s }, .done = false };
    Coroutine *co;

    while (!QLIST_EMPTY(&s->active_list)) {
        aio_poll(qemu_get_aio_context(), true);
    }

    co = qemu_coroutine_create(virtfs_co_reset, &data);
    qemu_coroutine_enter(co);

    while (!data.done) {
        aio_poll(qemu_get_aio_context(), true);
    }
}

Project: qemu
File: 9p.c

static void __attribute__((__constructor__)) v9fs_set_fd_limit(void)
{
    struct rlimit rlim;
    if (getrlimit(RLIMIT_NOFILE, &rlim) < 0) {
        error_report("Failed to get the resource limit");
        exit(1);
    }
    open_fd_hw = rlim.rlim_cur - MIN(400, rlim.rlim_cur/3);
    open_fd_rc = rlim.rlim_cur/2;
}

Project: qemu
File: 9p.c

static void coroutine_fn v9fs_setattr(void *opaque)
{
    int err = 0;
    int32_t fid;
    V9fsFidState *fidp;
    size_t offset = 7;
    V9fsIattr v9iattr;
    V9fsPDU *pdu = opaque;

    err = pdu_unmarshal(pdu, offset, "dI", &fid, &v9iattr);
    if (err < 0) {
        goto out_nofid;
    }

    fidp = get_fid(pdu, fid);
    if (fidp == NULL) {
        err = -EINVAL;
        goto out_nofid;
    }
    if (v9iattr.valid & P9_ATTR_MODE) {
        err = v9fs_co_chmod(pdu, &fidp->path, v9iattr.mode);
        if (err < 0) {
            goto out;
        }
    }
    if (v9iattr.valid & (P9_ATTR_ATIME | P9_ATTR_MTIME)) {
        struct timespec times[2];
        if (v9iattr.valid & P9_ATTR_ATIME) {
            if (v9iattr.valid & P9_ATTR_ATIME_SET) {
                times[0].tv_sec = v9iattr.atime_sec;
                times[0].tv_nsec = v9iattr.atime_nsec;
            } else {
                times[0].tv_nsec = UTIME_NOW;
            }
        } else {
            times[0].tv_nsec = UTIME_OMIT;
        }
        if (v9iattr.valid & P9_ATTR_MTIME) {
            if (v9iattr.valid & P9_ATTR_MTIME_SET) {
                times[1].tv_sec = v9iattr.mtime_sec;
                times[1].tv_nsec = v9iattr.mtime_nsec;
            } else {
                times[1].tv_nsec = UTIME_NOW;
            }
        } else {
            times[1].tv_nsec = UTIME_OMIT;
        }
        err = v9fs_co_utimensat(pdu, &fidp->path, times);
        if (err < 0) {
            goto out;
        }
    }
    /*
     * If the only valid entry in iattr is ctime we can call
     * chown(-1,-1) to update the ctime of the file
     */
    if ((v9iattr.valid & (P9_ATTR_UID | P9_ATTR_GID)) ||
        ((v9iattr.valid & P9_ATTR_CTIME)
         && !((v9iattr.valid & P9_ATTR_MASK) & ~P9_ATTR_CTIME))) {
        if (!(v9iattr.valid & P9_ATTR_UID)) {
            v9iattr.uid = -1;
        }
        if (!(v9iattr.valid & P9_ATTR_GID)) {
            v9iattr.gid = -1;
        }
        err = v9fs_co_chown(pdu, &fidp->path, v9iattr.uid,
                            v9iattr.gid);
        if (err < 0) {
            goto out;
        }
    }
    if (v9iattr.valid & (P9_ATTR_SIZE)) {
        err = v9fs_co_truncate(pdu, &fidp->path, v9iattr.size);
        if (err < 0) {
            goto out;
        }
    }
    err = offset;
out:
    put_fid(pdu, fidp);
out_nofid:
    pdu_complete(pdu, err);
}

Project: qemu
File: 9p.c

static void coroutine_fn v9fs_stat(void *opaque)
{
    int32_t fid;
    V9fsStat v9stat;
    ssize_t err = 0;
    size_t offset = 7;
    struct stat stbuf;
    V9fsFidState *fidp;
    V9fsPDU *pdu = opaque;

    err = pdu_unmarshal(pdu, offset, "d", &fid);
    if (err < 0) {
        goto out_nofid;
    }
    trace_v9fs_stat(pdu->tag, pdu->id, fid);

    fidp = get_fid(pdu, fid);
    if (fidp == NULL) {
        err = -ENOENT;
        goto out_nofid;
    }
    err = v9fs_co_lstat(pdu, &fidp->path, &stbuf);
    if (err < 0) {
        goto out;
    }
    err = stat_to_v9stat(pdu, &fidp->path, &stbuf, &v9stat);
    if (err < 0) {
        goto out;
    }
    err = pdu_marshal(pdu, offset, "wS", 0, &v9stat);
    if (err < 0) {
        v9fs_stat_free(&v9stat);
        goto out;
    }
    trace_v9fs_stat_return(pdu->tag, pdu->id, v9stat.mode,
                           v9stat.atime, v9stat.mtime, v9stat.length);
    err += offset;
    v9fs_stat_free(&v9stat);
out:
    put_fid(pdu, fidp);
out_nofid:
    pdu_complete(pdu, err);
}

Project: qemu
File: 9p.c

static void coroutine_fn v9fs_statfs(void *opaque)
{
    int32_t fid;
    ssize_t retval = 0;
    size_t offset = 7;
    V9fsFidState *fidp;
    struct statfs stbuf;
    V9fsPDU *pdu = opaque;
    V9fsState *s = pdu->s;

    retval = pdu_unmarshal(pdu, offset, "d", &fid);
    if (retval < 0) {
        goto out_nofid;
    }
    fidp = get_fid(pdu, fid);
    if (fidp == NULL) {
        retval = -ENOENT;
        goto out_nofid;
    }
    retval = v9fs_co_statfs(pdu, &fidp->path, &stbuf);
    if (retval < 0) {
        goto out;
    }
    retval = v9fs_fill_statfs(s, pdu, &stbuf);
    if (retval < 0) {
        goto out;
    }
    retval += offset;
out:
    put_fid(pdu, fidp);
out_nofid:
    pdu_complete(pdu, retval);
}

Project: qemu
File: 9p.c

static void coroutine_fn v9fs_unlinkat(void *opaque)
{
    int err = 0;
    V9fsString name;
    int32_t dfid, flags;
    size_t offset = 7;
    V9fsPath path;
    V9fsFidState *dfidp;
    V9fsPDU *pdu = opaque;

    v9fs_string_init(&name);
    err = pdu_unmarshal(pdu, offset, "dsd", &dfid, &name, &flags);
    if (err < 0) {
        goto out_nofid;
    }

    if (name_is_illegal(name.data)) {
        err = -ENOENT;
        goto out_nofid;
    }

    if (!strcmp(".", name.data)) {
        err = -EINVAL;
        goto out_nofid;
    }

    if (!strcmp("..", name.data)) {
        err = -ENOTEMPTY;
        goto out_nofid;
    }

    dfidp = get_fid(pdu, dfid);
    if (dfidp == NULL) {
        err = -EINVAL;
        goto out_nofid;
    }
    /*
     * IF the file is unlinked, we cannot reopen
     * the file later. So don't reclaim fd
     */
    v9fs_path_init(&path);
    err = v9fs_co_name_to_path(pdu, &dfidp->path, name.data, &path);
    if (err < 0) {
        goto out_err;
    }
    err = v9fs_mark_fids_unreclaim(pdu, &path);
    if (err < 0) {
        goto out_err;
    }
    err = v9fs_co_unlinkat(pdu, &dfidp->path, &name, flags);
    if (!err) {
        err = offset;
    }
out_err:
    put_fid(pdu, dfidp);
    v9fs_path_free(&path);
out_nofid:
    pdu_complete(pdu, err);
    v9fs_string_free(&name);
}

Project: qemu
File: 9p.c

static void coroutine_fn v9fs_version(void *opaque)
{
    ssize_t err;
    V9fsPDU *pdu = opaque;
    V9fsState *s = pdu->s;
    V9fsString version;
    size_t offset = 7;

    v9fs_string_init(&version);
    err = pdu_unmarshal(pdu, offset, "ds", &s->msize, &version);
    if (err < 0) {
        offset = err;
        goto out;
    }
    trace_v9fs_version(pdu->tag, pdu->id, s->msize, version.data);

    virtfs_reset(pdu);

    if (!strcmp(version.data, "9P2000.u")) {
        s->proto_version = V9FS_PROTO_2000U;
    } else if (!strcmp(version.data, "9P2000.L")) {
        s->proto_version = V9FS_PROTO_2000L;
    } else {
        v9fs_string_sprintf(&version, "unknown");
    }

    err = pdu_marshal(pdu, offset, "ds", s->msize, &version);
    if (err < 0) {
        offset = err;
        goto out;
    }
    offset += err;
    trace_v9fs_version_return(pdu->tag, pdu->id, s->msize, version.data);
out:
    pdu_complete(pdu, offset);
    v9fs_string_free(&version);
}

Project: qemu
File: 9p.c

static void coroutine_fn v9fs_walk(void *opaque)
{
    int name_idx;
    V9fsQID *qids = NULL;
    int i, err = 0;
    V9fsPath dpath, path;
    uint16_t nwnames;
    struct stat stbuf;
    size_t offset = 7;
    int32_t fid, newfid;
    V9fsString *wnames = NULL;
    V9fsFidState *fidp;
    V9fsFidState *newfidp = NULL;
    V9fsPDU *pdu = opaque;
    V9fsState *s = pdu->s;
    V9fsQID qid;

    err = pdu_unmarshal(pdu, offset, "ddw", &fid, &newfid, &nwnames);
    if (err < 0) {
        pdu_complete(pdu, err);
        return ;
    }
    offset += err;

    trace_v9fs_walk(pdu->tag, pdu->id, fid, newfid, nwnames);

    if (nwnames && nwnames <= P9_MAXWELEM) {
        wnames = g_malloc0(sizeof(wnames[0]) * nwnames);
        qids   = g_malloc0(sizeof(qids[0]) * nwnames);
        for (i = 0; i < nwnames; i++) {
            err = pdu_unmarshal(pdu, offset, "s", &wnames[i]);
            if (err < 0) {
                goto out_nofid;
            }
            if (name_is_illegal(wnames[i].data)) {
                err = -ENOENT;
                goto out_nofid;
            }
            offset += err;
        }
    } else if (nwnames > P9_MAXWELEM) {
        err = -EINVAL;
        goto out_nofid;
    }
    fidp = get_fid(pdu, fid);
    if (fidp == NULL) {
        err = -ENOENT;
        goto out_nofid;
    }

    v9fs_path_init(&dpath);
    v9fs_path_init(&path);

    err = fid_to_qid(pdu, fidp, &qid);
    if (err < 0) {
        goto out;
    }

    /*
     * Both dpath and path initially poin to fidp.
     * Needed to handle request with nwnames == 0
     */
    v9fs_path_copy(&dpath, &fidp->path);
    v9fs_path_copy(&path, &fidp->path);
    for (name_idx = 0; name_idx < nwnames; name_idx++) {
        if (not_same_qid(&pdu->s->root_qid, &qid) ||
            strcmp("..", wnames[name_idx].data)) {
            err = v9fs_co_name_to_path(pdu, &dpath, wnames[name_idx].data,
                                       &path);
            if (err < 0) {
                goto out;
            }

            err = v9fs_co_lstat(pdu, &path, &stbuf);
            if (err < 0) {
                goto out;
            }
            stat_to_qid(&stbuf, &qid);
            v9fs_path_copy(&dpath, &path);
        }
        memcpy(&qids[name_idx], &qid, sizeof(qid));
    }
    if (fid == newfid) {
        if (fidp->fid_type != P9_FID_NONE) {
            err = -EINVAL;
            goto out;
        }
        v9fs_path_copy(&fidp->path, &path);
    } else {
        newfidp = alloc_fid(s, newfid);
        if (newfidp == NULL) {
            err = -EINVAL;
            goto out;
        }
        newfidp->uid = fidp->uid;
        v9fs_path_copy(&newfidp->path, &path);
    }
    err = v9fs_walk_marshal(pdu, nwnames, qids);
    trace_v9fs_walk_return(pdu->tag, pdu->id, nwnames, qids);
out:
    put_fid(pdu, fidp);
    if (newfidp) {
        put_fid(pdu, newfidp);
    }
    v9fs_path_free(&dpath);
    v9fs_path_free(&path);
out_nofid:
    pdu_complete(pdu, err);
    if (nwnames && nwnames <= P9_MAXWELEM) {
        for (name_idx = 0; name_idx < nwnames; name_idx++) {
            v9fs_string_free(&wnames[name_idx]);
        }
        g_free(wnames);
        g_free(qids);
    }
}

Project: qemu
File: 9p.c

static void coroutine_fn v9fs_wstat(void *opaque)
{
    int32_t fid;
    int err = 0;
    int16_t unused;
    V9fsStat v9stat;
    size_t offset = 7;
    struct stat stbuf;
    V9fsFidState *fidp;
    V9fsPDU *pdu = opaque;

    v9fs_stat_init(&v9stat);
    err = pdu_unmarshal(pdu, offset, "dwS", &fid, &unused, &v9stat);
    if (err < 0) {
        goto out_nofid;
    }
    trace_v9fs_wstat(pdu->tag, pdu->id, fid,
                     v9stat.mode, v9stat.atime, v9stat.mtime);

    fidp = get_fid(pdu, fid);
    if (fidp == NULL) {
        err = -EINVAL;
        goto out_nofid;
    }
    /* do we need to sync the file? */
    if (donttouch_stat(&v9stat)) {
        err = v9fs_co_fsync(pdu, fidp, 0);
        goto out;
    }
    if (v9stat.mode != -1) {
        uint32_t v9_mode;
        err = v9fs_co_lstat(pdu, &fidp->path, &stbuf);
        if (err < 0) {
            goto out;
        }
        v9_mode = stat_to_v9mode(&stbuf);
        if ((v9stat.mode & P9_STAT_MODE_TYPE_BITS) !=
            (v9_mode & P9_STAT_MODE_TYPE_BITS)) {
            /* Attempting to change the type */
            err = -EIO;
            goto out;
        }
        err = v9fs_co_chmod(pdu, &fidp->path,
                            v9mode_to_mode(v9stat.mode,
                                           &v9stat.extension));
        if (err < 0) {
            goto out;
        }
    }
    if (v9stat.mtime != -1 || v9stat.atime != -1) {
        struct timespec times[2];
        if (v9stat.atime != -1) {
            times[0].tv_sec = v9stat.atime;
            times[0].tv_nsec = 0;
        } else {
            times[0].tv_nsec = UTIME_OMIT;
        }
        if (v9stat.mtime != -1) {
            times[1].tv_sec = v9stat.mtime;
            times[1].tv_nsec = 0;
        } else {
            times[1].tv_nsec = UTIME_OMIT;
        }
        err = v9fs_co_utimensat(pdu, &fidp->path, times);
        if (err < 0) {
            goto out;
        }
    }
    if (v9stat.n_gid != -1 || v9stat.n_uid != -1) {
        err = v9fs_co_chown(pdu, &fidp->path, v9stat.n_uid, v9stat.n_gid);
        if (err < 0) {
            goto out;
        }
    }
    if (v9stat.name.size != 0) {
        err = v9fs_complete_rename(pdu, fidp, -1, &v9stat.name);
        if (err < 0) {
            goto out;
        }
    }
    if (v9stat.length != -1) {
        err = v9fs_co_truncate(pdu, &fidp->path, v9stat.length);
        if (err < 0) {
            goto out;
        }
    }
    err = offset;
out:
    put_fid(pdu, fidp);
out_nofid:
    v9fs_stat_free(&v9stat);
    pdu_complete(pdu, err);
}

Project: qemu
File: 9p.c

static int v9fs_xattr_read(V9fsState *s, V9fsPDU *pdu, V9fsFidState *fidp,
                           uint64_t off, uint32_t max_count)
{
    ssize_t err;
    size_t offset = 7;
    uint64_t read_count;
    V9fsVirtioState *v = container_of(s, V9fsVirtioState, state);
    VirtQueueElement *elem = v->elems[pdu->idx];

    if (fidp->fs.xattr.len < off) {
        read_count = 0;
    } else {
        read_count = fidp->fs.xattr.len - off;
    }
    if (read_count > max_count) {
        read_count = max_count;
    }
    err = pdu_marshal(pdu, offset, "d", read_count);
    if (err < 0) {
        return err;
    }
    offset += err;

    err = v9fs_pack(elem->in_sg, elem->in_num, offset,
                    ((char *)fidp->fs.xattr.value) + off,
                    read_count);
    if (err < 0) {
        return err;
    }
    offset += err;
    return offset;
}

Project: qemu
File: 9p.c

static void coroutine_fn v9fs_xattrcreate(void *opaque)
{
    int flags;
    int32_t fid;
    uint64_t size;
    ssize_t err = 0;
    V9fsString name;
    size_t offset = 7;
    V9fsFidState *file_fidp;
    V9fsFidState *xattr_fidp;
    V9fsPDU *pdu = opaque;

    v9fs_string_init(&name);
    err = pdu_unmarshal(pdu, offset, "dsqd", &fid, &name, &size, &flags);
    if (err < 0) {
        goto out_nofid;
    }
    trace_v9fs_xattrcreate(pdu->tag, pdu->id, fid, name.data, size, flags);

    if (size > XATTR_SIZE_MAX) {
        err = -E2BIG;
        goto out_nofid;
    }

    file_fidp = get_fid(pdu, fid);
    if (file_fidp == NULL) {
        err = -EINVAL;
        goto out_nofid;
    }
    if (file_fidp->fid_type != P9_FID_NONE) {
        err = -EINVAL;
        goto out_put_fid;
    }

    /* Make the file fid point to xattr */
    xattr_fidp = file_fidp;
    xattr_fidp->fid_type = P9_FID_XATTR;
    xattr_fidp->fs.xattr.copied_len = 0;
    xattr_fidp->fs.xattr.xattrwalk_fid = false;
    xattr_fidp->fs.xattr.len = size;
    xattr_fidp->fs.xattr.flags = flags;
    v9fs_string_init(&xattr_fidp->fs.xattr.name);
    v9fs_string_copy(&xattr_fidp->fs.xattr.name, &name);
    xattr_fidp->fs.xattr.value = g_malloc0(size);
    err = offset;
out_put_fid:
    put_fid(pdu, file_fidp);
out_nofid:
    pdu_complete(pdu, err);
    v9fs_string_free(&name);
}

Project: qemu
File: 9p.c

static void coroutine_fn v9fs_xattrwalk(void *opaque)
{
    int64_t size;
    V9fsString name;
    ssize_t err = 0;
    size_t offset = 7;
    int32_t fid, newfid;
    V9fsFidState *file_fidp;
    V9fsFidState *xattr_fidp = NULL;
    V9fsPDU *pdu = opaque;
    V9fsState *s = pdu->s;

    v9fs_string_init(&name);
    err = pdu_unmarshal(pdu, offset, "dds", &fid, &newfid, &name);
    if (err < 0) {
        goto out_nofid;
    }
    trace_v9fs_xattrwalk(pdu->tag, pdu->id, fid, newfid, name.data);

    file_fidp = get_fid(pdu, fid);
    if (file_fidp == NULL) {
        err = -ENOENT;
        goto out_nofid;
    }
    xattr_fidp = alloc_fid(s, newfid);
    if (xattr_fidp == NULL) {
        err = -EINVAL;
        goto out;
    }
    v9fs_path_copy(&xattr_fidp->path, &file_fidp->path);
    if (!v9fs_string_size(&name)) {
        /*
         * listxattr request. Get the size first
         */
        size = v9fs_co_llistxattr(pdu, &xattr_fidp->path, NULL, 0);
        if (size < 0) {
            err = size;
            clunk_fid(s, xattr_fidp->fid);
            goto out;
        }
        /*
         * Read the xattr value
         */
        xattr_fidp->fs.xattr.len = size;
        xattr_fidp->fid_type = P9_FID_XATTR;
        xattr_fidp->fs.xattr.xattrwalk_fid = true;
        if (size) {
            xattr_fidp->fs.xattr.value = g_malloc(size);
            err = v9fs_co_llistxattr(pdu, &xattr_fidp->path,
                                     xattr_fidp->fs.xattr.value,
                                     xattr_fidp->fs.xattr.len);
            if (err < 0) {
                clunk_fid(s, xattr_fidp->fid);
                goto out;
            }
        }
        err = pdu_marshal(pdu, offset, "q", size);
        if (err < 0) {
            goto out;
        }
        err += offset;
    } else {
        /*
         * specific xattr fid. We check for xattr
         * presence also collect the xattr size
         */
        size = v9fs_co_lgetxattr(pdu, &xattr_fidp->path,
                                 &name, NULL, 0);
        if (size < 0) {
            err = size;
            clunk_fid(s, xattr_fidp->fid);
            goto out;
        }
        /*
         * Read the xattr value
         */
        xattr_fidp->fs.xattr.len = size;
        xattr_fidp->fid_type = P9_FID_XATTR;
        xattr_fidp->fs.xattr.xattrwalk_fid = true;
        if (size) {
            xattr_fidp->fs.xattr.value = g_malloc(size);
            err = v9fs_co_lgetxattr(pdu, &xattr_fidp->path,
                                    &name, xattr_fidp->fs.xattr.value,
                                    xattr_fidp->fs.xattr.len);
            if (err < 0) {
                clunk_fid(s, xattr_fidp->fid);
                goto out;
            }
        }
        err = pdu_marshal(pdu, offset, "q", size);
        if (err < 0) {
            goto out;
        }
        err += offset;
    }
    trace_v9fs_xattrwalk_return(pdu->tag, pdu->id, size);
out:
    put_fid(pdu, file_fidp);
    if (xattr_fidp) {
        put_fid(pdu, xattr_fidp);
    }
out_nofid:
    pdu_complete(pdu, err);
    v9fs_string_free(&name);
}

Project: qemu
File: 9p.c

static void coroutine_fn virtfs_co_reset(void *opaque)
{
    VirtfsCoResetData *data = opaque;

    virtfs_reset(&data->pdu);
    data->done = true;
}