9pfs changes:

* Fixes for file descriptor reclaiming algorithm (i.e. when running
   towards host's allowed limit of max. open file descriptors).
 
 * Additional fixes on use-after-unlink idiom (i.e. client operations on a
   file descriptor after file has been removed).
 -----BEGIN PGP SIGNATURE-----
 
 iQJLBAABCgA1FiEEltjREM96+AhPiFkBNMK1h2Wkc5UFAmgYie0XHHFlbXVfb3Nz
 QGNydWRlYnl0ZS5jb20ACgkQNMK1h2Wkc5XbDRAAq5SW7Hxifdhf1ZRBtkVOD88q
 Iw/OrMLIke4pCQwRElCDrE0mhycqyUpNX67eIye7qx0dJl2btFQUI9L6YuCDFtcG
 fPORZl51V81BOXqS8MhbK1oDxidl+cnpA8GcA1OyhYjxBifOy/x/0KG0pZVwzi0Y
 jhAIdsfeSenTE0Zzb02oh9mVmlMtKnwrSz7R0IB3Sv575CQiO76OM5B9sps1TUPu
 NrnQYBIB+EwJnI+l9NOKzNa7AUxV/S73OFCyJkQCON2ZHWiVadgXxjlX3kHyh9oL
 3uiiTdC2694jU0RaVMMSNLfdIG4YK2GkKPHM7qLYF8Kdc5QogEJifS/RoihCnZFR
 X72G7mOVo8/7goRBt3DGQCwz3eUgqTO9iPFn1hJRvx9x/CVlFi2eOP+5nHR5PMEO
 qSY2of6LziCslNXvxjjhf7HmRhlugkHqpr+UGTxwMGazr88bHKNFbsh/3BcTmWwW
 /wGRfEFse3exgFiCtoebavxbJaUeI0Y93S4KidOhhqrQFz24k2AElgFrb1gEpbht
 GWW8YEblL7Lj8mecFATXKiInHCyhVPFmuAO//Wbu9juJVcNPtl67f017bCR+90H3
 GrRJqorHrp6icGQmXSM+Qdrr3B21RZwqb3W4mdMOWN3Zg5bHPHJ6rx8BRe7qDHBH
 mWtvrsUfcL0sRW0nkgc=
 =hfW6
 -----END PGP SIGNATURE-----

Merge tag 'pull-9p-20250505' of https://github.com/cschoenebeck/qemu into staging

9pfs changes:

* Fixes for file descriptor reclaiming algorithm (i.e. when running
  towards host's allowed limit of max. open file descriptors).

* Additional fixes on use-after-unlink idiom (i.e. client operations on a
  file descriptor after file has been removed).

# -----BEGIN PGP SIGNATURE-----
#
# iQJLBAABCgA1FiEEltjREM96+AhPiFkBNMK1h2Wkc5UFAmgYie0XHHFlbXVfb3Nz
# QGNydWRlYnl0ZS5jb20ACgkQNMK1h2Wkc5XbDRAAq5SW7Hxifdhf1ZRBtkVOD88q
# Iw/OrMLIke4pCQwRElCDrE0mhycqyUpNX67eIye7qx0dJl2btFQUI9L6YuCDFtcG
# fPORZl51V81BOXqS8MhbK1oDxidl+cnpA8GcA1OyhYjxBifOy/x/0KG0pZVwzi0Y
# jhAIdsfeSenTE0Zzb02oh9mVmlMtKnwrSz7R0IB3Sv575CQiO76OM5B9sps1TUPu
# NrnQYBIB+EwJnI+l9NOKzNa7AUxV/S73OFCyJkQCON2ZHWiVadgXxjlX3kHyh9oL
# 3uiiTdC2694jU0RaVMMSNLfdIG4YK2GkKPHM7qLYF8Kdc5QogEJifS/RoihCnZFR
# X72G7mOVo8/7goRBt3DGQCwz3eUgqTO9iPFn1hJRvx9x/CVlFi2eOP+5nHR5PMEO
# qSY2of6LziCslNXvxjjhf7HmRhlugkHqpr+UGTxwMGazr88bHKNFbsh/3BcTmWwW
# /wGRfEFse3exgFiCtoebavxbJaUeI0Y93S4KidOhhqrQFz24k2AElgFrb1gEpbht
# GWW8YEblL7Lj8mecFATXKiInHCyhVPFmuAO//Wbu9juJVcNPtl67f017bCR+90H3
# GrRJqorHrp6icGQmXSM+Qdrr3B21RZwqb3W4mdMOWN3Zg5bHPHJ6rx8BRe7qDHBH
# mWtvrsUfcL0sRW0nkgc=
# =hfW6
# -----END PGP SIGNATURE-----
# gpg: Signature made Mon 05 May 2025 05:50:37 EDT
# gpg:                using RSA key 96D8D110CF7AF8084F88590134C2B58765A47395
# gpg:                issuer "qemu_oss@crudebyte.com"
# gpg: Good signature from "Christian Schoenebeck <qemu_oss@crudebyte.com>" [unknown]
# gpg: WARNING: The key's User ID is not certified with a trusted signature!
# gpg:          There is no indication that the signature belongs to the owner.
# Primary key fingerprint: ECAB 1A45 4014 1413 BA38  4926 30DB 47C3 A012 D5F4
#      Subkey fingerprint: 96D8 D110 CF7A F808 4F88  5901 34C2 B587 65A4 7395

* tag 'pull-9p-20250505' of https://github.com/cschoenebeck/qemu:
  9pfs: fix 'total_open_fd' decrementation
  tests/9p: Test `Tsetattr` can truncate unlinked file
  tests/9p: add 'Tsetattr' request to test client
  9pfs: Introduce futimens file op
  9pfs: Introduce ftruncate file op
  9pfs: Don't use file descriptors in core code
  9pfs: local : Introduce local_fid_fd() helper
  9pfs: fix FD leak and reduce latency of v9fs_reclaim_fd()
  9pfs: fix concurrent v9fs_reclaim_fd() calls

Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
Stefan Hajnoczi 2025-05-05 11:26:59 -04:00
commit a9e0c9c0f1
13 changed files with 271 additions and 30 deletions

View File

@ -129,6 +129,8 @@ struct FileOperations {
int (*chown)(FsContext *, V9fsPath *, FsCred *); int (*chown)(FsContext *, V9fsPath *, FsCred *);
int (*mknod)(FsContext *, V9fsPath *, const char *, FsCred *); int (*mknod)(FsContext *, V9fsPath *, const char *, FsCred *);
int (*utimensat)(FsContext *, V9fsPath *, const struct timespec *); int (*utimensat)(FsContext *, V9fsPath *, const struct timespec *);
int (*futimens)(FsContext *ctx, int fid_type, V9fsFidOpenState *fs,
const struct timespec *times);
int (*remove)(FsContext *, const char *); int (*remove)(FsContext *, const char *);
int (*symlink)(FsContext *, const char *, V9fsPath *, int (*symlink)(FsContext *, const char *, V9fsPath *,
const char *, FsCred *); const char *, FsCred *);
@ -152,6 +154,8 @@ struct FileOperations {
int (*fstat)(FsContext *, int, V9fsFidOpenState *, struct stat *); int (*fstat)(FsContext *, int, V9fsFidOpenState *, struct stat *);
int (*rename)(FsContext *, const char *, const char *); int (*rename)(FsContext *, const char *, const char *);
int (*truncate)(FsContext *, V9fsPath *, off_t); int (*truncate)(FsContext *, V9fsPath *, off_t);
int (*ftruncate)(FsContext *ctx, int fid_type, V9fsFidOpenState *fs,
off_t size);
int (*fsync)(FsContext *, int, V9fsFidOpenState *, int); int (*fsync)(FsContext *, int, V9fsFidOpenState *, int);
int (*statfs)(FsContext *s, V9fsPath *path, struct statfs *stbuf); int (*statfs)(FsContext *s, V9fsPath *path, struct statfs *stbuf);
ssize_t (*lgetxattr)(FsContext *, V9fsPath *, ssize_t (*lgetxattr)(FsContext *, V9fsPath *,
@ -164,6 +168,7 @@ struct FileOperations {
int (*renameat)(FsContext *ctx, V9fsPath *olddir, const char *old_name, int (*renameat)(FsContext *ctx, V9fsPath *olddir, const char *old_name,
V9fsPath *newdir, const char *new_name); V9fsPath *newdir, const char *new_name);
int (*unlinkat)(FsContext *ctx, V9fsPath *dir, const char *name, int flags); int (*unlinkat)(FsContext *ctx, V9fsPath *dir, const char *name, int flags);
bool (*has_valid_file_handle)(int fid_type, V9fsFidOpenState *fs);
}; };
#endif #endif

View File

@ -766,16 +766,19 @@ out:
return err; return err;
} }
static int local_fid_fd(int fid_type, V9fsFidOpenState *fs)
{
if (fid_type == P9_FID_DIR) {
return dirfd(fs->dir.stream);
} else {
return fs->fd;
}
}
static int local_fstat(FsContext *fs_ctx, int fid_type, static int local_fstat(FsContext *fs_ctx, int fid_type,
V9fsFidOpenState *fs, struct stat *stbuf) V9fsFidOpenState *fs, struct stat *stbuf)
{ {
int err, fd; int err, fd = local_fid_fd(fid_type, fs);
if (fid_type == P9_FID_DIR) {
fd = dirfd(fs->dir.stream);
} else {
fd = fs->fd;
}
err = fstat(fd, stbuf); err = fstat(fd, stbuf);
if (err) { if (err) {
@ -1039,6 +1042,14 @@ static int local_truncate(FsContext *ctx, V9fsPath *fs_path, off_t size)
return ret; return ret;
} }
static int local_ftruncate(FsContext *ctx, int fid_type, V9fsFidOpenState *fs,
off_t size)
{
int fd = local_fid_fd(fid_type, fs);
return ftruncate(fd, size);
}
static int local_chown(FsContext *fs_ctx, V9fsPath *fs_path, FsCred *credp) static int local_chown(FsContext *fs_ctx, V9fsPath *fs_path, FsCred *credp)
{ {
char *dirpath = g_path_get_dirname(fs_path->data); char *dirpath = g_path_get_dirname(fs_path->data);
@ -1089,6 +1100,14 @@ out:
return ret; return ret;
} }
static int local_futimens(FsContext *s, int fid_type, V9fsFidOpenState *fs,
const struct timespec *times)
{
int fd = local_fid_fd(fid_type, fs);
return qemu_futimens(fd, times);
}
static int local_unlinkat_common(FsContext *ctx, int dirfd, const char *name, static int local_unlinkat_common(FsContext *ctx, int dirfd, const char *name,
int flags) int flags)
{ {
@ -1167,13 +1186,7 @@ out:
static int local_fsync(FsContext *ctx, int fid_type, static int local_fsync(FsContext *ctx, int fid_type,
V9fsFidOpenState *fs, int datasync) V9fsFidOpenState *fs, int datasync)
{ {
int fd; int fd = local_fid_fd(fid_type, fs);
if (fid_type == P9_FID_DIR) {
fd = dirfd(fs->dir.stream);
} else {
fd = fs->fd;
}
if (datasync) { if (datasync) {
return qemu_fdatasync(fd); return qemu_fdatasync(fd);
@ -1575,6 +1588,13 @@ static int local_parse_opts(QemuOpts *opts, FsDriverEntry *fse, Error **errp)
return 0; return 0;
} }
static bool local_has_valid_file_handle(int fid_type, V9fsFidOpenState *fs)
{
return
(fid_type == P9_FID_FILE && fs->fd != -1) ||
(fid_type == P9_FID_DIR && fs->dir.stream != NULL);
}
FileOperations local_ops = { FileOperations local_ops = {
.parse_opts = local_parse_opts, .parse_opts = local_parse_opts,
.init = local_init, .init = local_init,
@ -1612,4 +1632,7 @@ FileOperations local_ops = {
.name_to_path = local_name_to_path, .name_to_path = local_name_to_path,
.renameat = local_renameat, .renameat = local_renameat,
.unlinkat = local_unlinkat, .unlinkat = local_unlinkat,
.has_valid_file_handle = local_has_valid_file_handle,
.ftruncate = local_ftruncate,
.futimens = local_futimens,
}; };

View File

@ -356,6 +356,13 @@ static int synth_truncate(FsContext *ctx, V9fsPath *path, off_t offset)
return -1; return -1;
} }
static int synth_ftruncate(FsContext *ctx, int fid_type, V9fsFidOpenState *fs,
off_t size)
{
errno = ENOSYS;
return -1;
}
static int synth_chmod(FsContext *fs_ctx, V9fsPath *path, FsCred *credp) static int synth_chmod(FsContext *fs_ctx, V9fsPath *path, FsCred *credp)
{ {
errno = EPERM; errno = EPERM;
@ -417,6 +424,13 @@ static int synth_utimensat(FsContext *fs_ctx, V9fsPath *path,
return 0; return 0;
} }
static int synth_futimens(FsContext *fs_ctx, int fid_type, V9fsFidOpenState *fs,
const struct timespec *buf)
{
errno = ENOSYS;
return -1;
}
static int synth_remove(FsContext *ctx, const char *path) static int synth_remove(FsContext *ctx, const char *path)
{ {
errno = EPERM; errno = EPERM;
@ -615,6 +629,11 @@ static int synth_init(FsContext *ctx, Error **errp)
return 0; return 0;
} }
static bool synth_has_valid_file_handle(int fid_type, V9fsFidOpenState *fs)
{
return false;
}
FileOperations synth_ops = { FileOperations synth_ops = {
.init = synth_init, .init = synth_init,
.lstat = synth_lstat, .lstat = synth_lstat,
@ -650,4 +669,7 @@ FileOperations synth_ops = {
.name_to_path = synth_name_to_path, .name_to_path = synth_name_to_path,
.renameat = synth_renameat, .renameat = synth_renameat,
.unlinkat = synth_unlinkat, .unlinkat = synth_unlinkat,
.has_valid_file_handle = synth_has_valid_file_handle,
.ftruncate = synth_ftruncate,
.futimens = synth_futimens,
}; };

View File

@ -103,6 +103,7 @@ static inline int errno_to_dotl(int err) {
#define qemu_renameat renameat #define qemu_renameat renameat
#define qemu_utimensat utimensat #define qemu_utimensat utimensat
#define qemu_unlinkat unlinkat #define qemu_unlinkat unlinkat
#define qemu_futimens futimens
static inline void close_preserve_errno(int fd) static inline void close_preserve_errno(int fd)
{ {

View File

@ -434,16 +434,24 @@ void coroutine_fn v9fs_reclaim_fd(V9fsPDU *pdu)
V9fsFidState *f; V9fsFidState *f;
GHashTableIter iter; GHashTableIter iter;
gpointer fid; gpointer fid;
int err;
int nclosed = 0;
/* prevent multiple coroutines running this function simultaniously */
if (s->reclaiming) {
return;
}
s->reclaiming = true;
g_hash_table_iter_init(&iter, s->fids); g_hash_table_iter_init(&iter, s->fids);
QSLIST_HEAD(, V9fsFidState) reclaim_list = QSLIST_HEAD(, V9fsFidState) reclaim_list =
QSLIST_HEAD_INITIALIZER(reclaim_list); QSLIST_HEAD_INITIALIZER(reclaim_list);
/* Pick FIDs to be closed, collect them on reclaim_list. */
while (g_hash_table_iter_next(&iter, &fid, (gpointer *) &f)) { while (g_hash_table_iter_next(&iter, &fid, (gpointer *) &f)) {
/* /*
* Unlink fids cannot be reclaimed. Check * Unlinked fids cannot be reclaimed, skip those, and also skip fids
* for them and skip them. Also skip fids
* currently being operated on. * currently being operated on.
*/ */
if (f->ref || f->flags & FID_NON_RECLAIMABLE) { if (f->ref || f->flags & FID_NON_RECLAIMABLE) {
@ -493,23 +501,42 @@ void coroutine_fn v9fs_reclaim_fd(V9fsPDU *pdu)
} }
} }
/* /*
* Now close the fid in reclaim list. Free them if they * Close the picked FIDs altogether on a background I/O driver thread. Do
* are already clunked. * this all at once to keep latency (i.e. amount of thread hops between main
* thread <-> fs driver background thread) as low as possible.
*/ */
v9fs_co_run_in_worker({
QSLIST_FOREACH(f, &reclaim_list, reclaim_next) {
err = (f->fid_type == P9_FID_DIR) ?
s->ops->closedir(&s->ctx, &f->fs_reclaim) :
s->ops->close(&s->ctx, &f->fs_reclaim);
/* 'man 2 close' suggests to ignore close() errors except of EBADF */
if (unlikely(err && errno == EBADF)) {
/*
* unexpected case as FIDs were picked above by having a valid
* file descriptor
*/
error_report("9pfs: v9fs_reclaim_fd() WARNING: close() failed with EBADF");
} else {
/* total_open_fd must only be mutated on main thread */
nclosed++;
}
}
});
total_open_fd -= nclosed;
/* Free the closed FIDs. */
while (!QSLIST_EMPTY(&reclaim_list)) { while (!QSLIST_EMPTY(&reclaim_list)) {
f = QSLIST_FIRST(&reclaim_list); f = QSLIST_FIRST(&reclaim_list);
QSLIST_REMOVE(&reclaim_list, f, V9fsFidState, reclaim_next); QSLIST_REMOVE(&reclaim_list, f, V9fsFidState, reclaim_next);
if (f->fid_type == P9_FID_FILE) {
v9fs_co_close(pdu, &f->fs_reclaim);
} else if (f->fid_type == P9_FID_DIR) {
v9fs_co_closedir(pdu, &f->fs_reclaim);
}
/* /*
* Now drop the fid reference, free it * Now drop the fid reference, free it
* if clunked. * if clunked.
*/ */
put_fid(pdu, f); put_fid(pdu, f);
} }
s->reclaiming = false;
} }
/* /*
@ -1574,6 +1601,11 @@ out_nofid:
pdu_complete(pdu, err); pdu_complete(pdu, err);
} }
static bool fid_has_valid_file_handle(V9fsState *s, V9fsFidState *fidp)
{
return s->ops->has_valid_file_handle(fidp->fid_type, &fidp->fs);
}
static void coroutine_fn v9fs_getattr(void *opaque) static void coroutine_fn v9fs_getattr(void *opaque)
{ {
int32_t fid; int32_t fid;
@ -1596,9 +1628,7 @@ static void coroutine_fn v9fs_getattr(void *opaque)
retval = -ENOENT; retval = -ENOENT;
goto out_nofid; goto out_nofid;
} }
if ((fidp->fid_type == P9_FID_FILE && fidp->fs.fd != -1) || if (fid_has_valid_file_handle(pdu->s, fidp)) {
(fidp->fid_type == P9_FID_DIR && fidp->fs.dir.stream))
{
retval = v9fs_co_fstat(pdu, fidp, &stbuf); retval = v9fs_co_fstat(pdu, fidp, &stbuf);
} else { } else {
retval = v9fs_co_lstat(pdu, &fidp->path, &stbuf); retval = v9fs_co_lstat(pdu, &fidp->path, &stbuf);
@ -1705,7 +1735,11 @@ static void coroutine_fn v9fs_setattr(void *opaque)
} else { } else {
times[1].tv_nsec = UTIME_OMIT; times[1].tv_nsec = UTIME_OMIT;
} }
err = v9fs_co_utimensat(pdu, &fidp->path, times); if (fid_has_valid_file_handle(pdu->s, fidp)) {
err = v9fs_co_futimens(pdu, fidp, times);
} else {
err = v9fs_co_utimensat(pdu, &fidp->path, times);
}
if (err < 0) { if (err < 0) {
goto out; goto out;
} }
@ -1730,7 +1764,11 @@ static void coroutine_fn v9fs_setattr(void *opaque)
} }
} }
if (v9iattr.valid & (P9_ATTR_SIZE)) { if (v9iattr.valid & (P9_ATTR_SIZE)) {
err = v9fs_co_truncate(pdu, &fidp->path, v9iattr.size); if (fid_has_valid_file_handle(pdu->s, fidp)) {
err = v9fs_co_ftruncate(pdu, fidp, v9iattr.size);
} else {
err = v9fs_co_truncate(pdu, &fidp->path, v9iattr.size);
}
if (err < 0) { if (err < 0) {
goto out; goto out;
} }
@ -4324,6 +4362,8 @@ int v9fs_device_realize_common(V9fsState *s, const V9fsTransport *t,
s->ctx.fst = &fse->fst; s->ctx.fst = &fse->fst;
fsdev_throttle_init(s->ctx.fst); fsdev_throttle_init(s->ctx.fst);
s->reclaiming = false;
rc = 0; rc = 0;
out: out:
if (rc) { if (rc) {

View File

@ -362,6 +362,7 @@ struct V9fsState {
uint64_t qp_ndevices; /* Amount of entries in qpd_table. */ uint64_t qp_ndevices; /* Amount of entries in qpd_table. */
uint16_t qp_affix_next; uint16_t qp_affix_next;
uint64_t qp_fullpath_next; uint64_t qp_fullpath_next;
bool reclaiming;
}; };
/* 9p2000.L open flags */ /* 9p2000.L open flags */

View File

@ -20,6 +20,7 @@
#include "fsdev/qemu-fsdev.h" #include "fsdev/qemu-fsdev.h"
#include "qemu/thread.h" #include "qemu/thread.h"
#include "qemu/main-loop.h" #include "qemu/main-loop.h"
#include "qemu/error-report.h"
#include "coth.h" #include "coth.h"
#include "9p-xattr.h" #include "9p-xattr.h"
#include "9p-util.h" #include "9p-util.h"
@ -353,7 +354,11 @@ int coroutine_fn v9fs_co_closedir(V9fsPDU *pdu, V9fsFidOpenState *fs)
err = -errno; err = -errno;
} }
}); });
if (!err) { /* 'man 2 close' suggests to ignore close() errors except of EBADF */
if (unlikely(err && errno == EBADF)) {
/* unexpected case as we should have checked for a valid file handle */
error_report("9pfs: WARNING: v9fs_co_closedir() failed with EBADF");
} else {
total_open_fd--; total_open_fd--;
} }
return err; return err;

View File

@ -20,6 +20,7 @@
#include "fsdev/qemu-fsdev.h" #include "fsdev/qemu-fsdev.h"
#include "qemu/thread.h" #include "qemu/thread.h"
#include "qemu/main-loop.h" #include "qemu/main-loop.h"
#include "qemu/error-report.h"
#include "coth.h" #include "coth.h"
int coroutine_fn v9fs_co_st_gen(V9fsPDU *pdu, V9fsPath *path, mode_t st_mode, int coroutine_fn v9fs_co_st_gen(V9fsPDU *pdu, V9fsPath *path, mode_t st_mode,
@ -197,7 +198,11 @@ int coroutine_fn v9fs_co_close(V9fsPDU *pdu, V9fsFidOpenState *fs)
err = -errno; err = -errno;
} }
}); });
if (!err) { /* 'man 2 close' suggests to ignore close() errors except of EBADF */
if (unlikely(err && errno == EBADF)) {
/* unexpected case as we should have checked for a valid file handle */
error_report("9pfs: WARNING: v9fs_co_close() failed with EBADF");
} else {
total_open_fd--; total_open_fd--;
} }
return err; return err;

View File

@ -139,6 +139,25 @@ int coroutine_fn v9fs_co_utimensat(V9fsPDU *pdu, V9fsPath *path,
return err; return err;
} }
int coroutine_fn v9fs_co_futimens(V9fsPDU *pdu, V9fsFidState *fidp,
struct timespec times[2])
{
int err;
V9fsState *s = pdu->s;
if (v9fs_request_cancelled(pdu)) {
return -EINTR;
}
v9fs_co_run_in_worker(
{
err = s->ops->futimens(&s->ctx, fidp->fid_type, &fidp->fs, times);
if (err < 0) {
err = -errno;
}
});
return err;
}
int coroutine_fn v9fs_co_chown(V9fsPDU *pdu, V9fsPath *path, uid_t uid, int coroutine_fn v9fs_co_chown(V9fsPDU *pdu, V9fsPath *path, uid_t uid,
gid_t gid) gid_t gid)
{ {
@ -184,6 +203,24 @@ int coroutine_fn v9fs_co_truncate(V9fsPDU *pdu, V9fsPath *path, off_t size)
return err; return err;
} }
int coroutine_fn v9fs_co_ftruncate(V9fsPDU *pdu, V9fsFidState *fidp, off_t size)
{
int err;
V9fsState *s = pdu->s;
if (v9fs_request_cancelled(pdu)) {
return -EINTR;
}
v9fs_co_run_in_worker(
{
err = s->ops->ftruncate(&s->ctx, fidp->fid_type, &fidp->fs, size);
if (err < 0) {
err = -errno;
}
});
return err;
}
int coroutine_fn v9fs_co_mknod(V9fsPDU *pdu, V9fsFidState *fidp, int coroutine_fn v9fs_co_mknod(V9fsPDU *pdu, V9fsFidState *fidp,
V9fsString *name, uid_t uid, gid_t gid, V9fsString *name, uid_t uid, gid_t gid,
dev_t dev, mode_t mode, struct stat *stbuf) dev_t dev, mode_t mode, struct stat *stbuf)

View File

@ -71,8 +71,12 @@ int coroutine_fn v9fs_co_statfs(V9fsPDU *, V9fsPath *, struct statfs *);
int coroutine_fn v9fs_co_lstat(V9fsPDU *, V9fsPath *, struct stat *); int coroutine_fn v9fs_co_lstat(V9fsPDU *, V9fsPath *, struct stat *);
int coroutine_fn v9fs_co_chmod(V9fsPDU *, V9fsPath *, mode_t); int coroutine_fn v9fs_co_chmod(V9fsPDU *, V9fsPath *, mode_t);
int coroutine_fn v9fs_co_utimensat(V9fsPDU *, V9fsPath *, struct timespec [2]); int coroutine_fn v9fs_co_utimensat(V9fsPDU *, V9fsPath *, struct timespec [2]);
int coroutine_fn v9fs_co_futimens(V9fsPDU *pdu, V9fsFidState *fidp,
struct timespec times[2]);
int coroutine_fn v9fs_co_chown(V9fsPDU *, V9fsPath *, uid_t, gid_t); int coroutine_fn v9fs_co_chown(V9fsPDU *, V9fsPath *, uid_t, gid_t);
int coroutine_fn v9fs_co_truncate(V9fsPDU *, V9fsPath *, off_t); int coroutine_fn v9fs_co_truncate(V9fsPDU *, V9fsPath *, off_t);
int coroutine_fn v9fs_co_ftruncate(V9fsPDU *pdu, V9fsFidState *fidp,
off_t size);
int coroutine_fn v9fs_co_llistxattr(V9fsPDU *, V9fsPath *, void *, size_t); int coroutine_fn v9fs_co_llistxattr(V9fsPDU *, V9fsPath *, void *, size_t);
int coroutine_fn v9fs_co_lgetxattr(V9fsPDU *, V9fsPath *, int coroutine_fn v9fs_co_lgetxattr(V9fsPDU *, V9fsPath *,
V9fsString *, void *, size_t); V9fsString *, void *, size_t);

View File

@ -557,6 +557,55 @@ void v9fs_rgetattr(P9Req *req, v9fs_attr *attr)
v9fs_req_free(req); v9fs_req_free(req);
} }
/*
* size[4] Tsetattr tag[2] fid[4] valid[4] mode[4] uid[4] gid[4] size[8]
* atime_sec[8] atime_nsec[8] mtime_sec[8] mtime_nsec[8]
*/
TSetAttrRes v9fs_tsetattr(TSetAttrOpt opt)
{
P9Req *req;
uint32_t err;
g_assert(opt.client);
req = v9fs_req_init(
opt.client, 4/*fid*/ + 4/*valid*/ + 4/*mode*/ + 4/*uid*/ + 4/*gid*/ +
8/*size*/ + 8/*atime_sec*/ + 8/*atime_nsec*/ + 8/*mtime_sec*/ +
8/*mtime_nsec*/, P9_TSETATTR, opt.tag
);
v9fs_uint32_write(req, opt.fid);
v9fs_uint32_write(req, (uint32_t) opt.attr.valid);
v9fs_uint32_write(req, opt.attr.mode);
v9fs_uint32_write(req, opt.attr.uid);
v9fs_uint32_write(req, opt.attr.gid);
v9fs_uint64_write(req, opt.attr.size);
v9fs_uint64_write(req, opt.attr.atime_sec);
v9fs_uint64_write(req, opt.attr.atime_nsec);
v9fs_uint64_write(req, opt.attr.mtime_sec);
v9fs_uint64_write(req, opt.attr.mtime_nsec);
v9fs_req_send(req);
if (!opt.requestOnly) {
v9fs_req_wait_for_reply(req, NULL);
if (opt.expectErr) {
v9fs_rlerror(req, &err);
g_assert_cmpint(err, ==, opt.expectErr);
} else {
v9fs_rsetattr(req);
}
req = NULL; /* request was freed */
}
return (TSetAttrRes) { .req = req };
}
/* size[4] Rsetattr tag[2] */
void v9fs_rsetattr(P9Req *req)
{
v9fs_req_recv(req, P9_RSETATTR);
v9fs_req_free(req);
}
/* size[4] Treaddir tag[2] fid[4] offset[8] count[4] */ /* size[4] Treaddir tag[2] fid[4] offset[8] count[4] */
TReadDirRes v9fs_treaddir(TReadDirOpt opt) TReadDirRes v9fs_treaddir(TReadDirOpt opt)
{ {

View File

@ -65,6 +65,16 @@ typedef struct v9fs_attr {
#define P9_GETATTR_BASIC 0x000007ffULL /* Mask for fields up to BLOCKS */ #define P9_GETATTR_BASIC 0x000007ffULL /* Mask for fields up to BLOCKS */
#define P9_GETATTR_ALL 0x00003fffULL /* Mask for ALL fields */ #define P9_GETATTR_ALL 0x00003fffULL /* Mask for ALL fields */
#define P9_SETATTR_MODE 0x00000001UL
#define P9_SETATTR_UID 0x00000002UL
#define P9_SETATTR_GID 0x00000004UL
#define P9_SETATTR_SIZE 0x00000008UL
#define P9_SETATTR_ATIME 0x00000010UL
#define P9_SETATTR_MTIME 0x00000020UL
#define P9_SETATTR_CTIME 0x00000040UL
#define P9_SETATTR_ATIME_SET 0x00000080UL
#define P9_SETATTR_MTIME_SET 0x00000100UL
struct V9fsDirent { struct V9fsDirent {
v9fs_qid qid; v9fs_qid qid;
uint64_t offset; uint64_t offset;
@ -182,6 +192,28 @@ typedef struct TGetAttrRes {
P9Req *req; P9Req *req;
} TGetAttrRes; } TGetAttrRes;
/* options for 'Tsetattr' 9p request */
typedef struct TSetAttrOpt {
/* 9P client being used (mandatory) */
QVirtio9P *client;
/* user supplied tag number being returned with response (optional) */
uint16_t tag;
/* file ID of file/dir whose attributes shall be modified (required) */
uint32_t fid;
/* new attribute values to be set by 9p server */
v9fs_attr attr;
/* only send Tsetattr request but not wait for a reply? (optional) */
bool requestOnly;
/* do we expect an Rlerror response, if yes which error code? (optional) */
uint32_t expectErr;
} TSetAttrOpt;
/* result of 'Tsetattr' 9p request */
typedef struct TSetAttrRes {
/* if requestOnly was set: request object for further processing */
P9Req *req;
} TSetAttrRes;
/* options for 'Treaddir' 9p request */ /* options for 'Treaddir' 9p request */
typedef struct TReadDirOpt { typedef struct TReadDirOpt {
/* 9P client being used (mandatory) */ /* 9P client being used (mandatory) */
@ -470,6 +502,8 @@ TWalkRes v9fs_twalk(TWalkOpt opt);
void v9fs_rwalk(P9Req *req, uint16_t *nwqid, v9fs_qid **wqid); void v9fs_rwalk(P9Req *req, uint16_t *nwqid, v9fs_qid **wqid);
TGetAttrRes v9fs_tgetattr(TGetAttrOpt); TGetAttrRes v9fs_tgetattr(TGetAttrOpt);
void v9fs_rgetattr(P9Req *req, v9fs_attr *attr); void v9fs_rgetattr(P9Req *req, v9fs_attr *attr);
TSetAttrRes v9fs_tsetattr(TSetAttrOpt opt);
void v9fs_rsetattr(P9Req *req);
TReadDirRes v9fs_treaddir(TReadDirOpt); TReadDirRes v9fs_treaddir(TReadDirOpt);
void v9fs_rreaddir(P9Req *req, uint32_t *count, uint32_t *nentries, void v9fs_rreaddir(P9Req *req, uint32_t *count, uint32_t *nentries,
struct V9fsDirent **entries); struct V9fsDirent **entries);

View File

@ -20,6 +20,7 @@
#define tversion(...) v9fs_tversion((TVersionOpt) __VA_ARGS__) #define tversion(...) v9fs_tversion((TVersionOpt) __VA_ARGS__)
#define tattach(...) v9fs_tattach((TAttachOpt) __VA_ARGS__) #define tattach(...) v9fs_tattach((TAttachOpt) __VA_ARGS__)
#define tgetattr(...) v9fs_tgetattr((TGetAttrOpt) __VA_ARGS__) #define tgetattr(...) v9fs_tgetattr((TGetAttrOpt) __VA_ARGS__)
#define tsetattr(...) v9fs_tsetattr((TSetAttrOpt) __VA_ARGS__)
#define treaddir(...) v9fs_treaddir((TReadDirOpt) __VA_ARGS__) #define treaddir(...) v9fs_treaddir((TReadDirOpt) __VA_ARGS__)
#define tlopen(...) v9fs_tlopen((TLOpenOpt) __VA_ARGS__) #define tlopen(...) v9fs_tlopen((TLOpenOpt) __VA_ARGS__)
#define twrite(...) v9fs_twrite((TWriteOpt) __VA_ARGS__) #define twrite(...) v9fs_twrite((TWriteOpt) __VA_ARGS__)
@ -735,6 +736,20 @@ static void fs_use_after_unlink(void *obj, void *data,
.data = buf .data = buf
}).count; }).count;
g_assert_cmpint(count, ==, write_count); g_assert_cmpint(count, ==, write_count);
/* truncate file to (arbitrarily chosen) size 2001 */
tsetattr({
.client = v9p, .fid = fid_file, .attr = (v9fs_attr) {
.valid = P9_SETATTR_SIZE,
.size = 2001
}
});
/* truncate apparently succeeded, let's double-check the size */
tgetattr({
.client = v9p, .fid = fid_file, .request_mask = P9_GETATTR_BASIC,
.rgetattr.attr = &attr
});
g_assert_cmpint(attr.size, ==, 2001);
} }
static void cleanup_9p_local_driver(void *data) static void cleanup_9p_local_driver(void *data)