blob: 93ba44fb75447ac65f83ef58941e903611441052 [file] [log] [blame]
/*
* 9p backend
*
* Copyright IBM, Corp. 2011
*
* Authors:
* Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
*
* This work is licensed under the terms of the GNU GPL, version 2. See
* the COPYING file in the top-level directory.
*
*/
/*
* Not so fast! You might want to read the 9p developer docs first:
* https://wiki.qemu.org/Documentation/9p
*/
#include "qemu/osdep.h"
#include "fsdev/qemu-fsdev.h"
#include "qemu/thread.h"
#include "qemu/coroutine.h"
#include "qemu/main-loop.h"
#include "coth.h"
#include "9p-xattr.h"
#include "9p-util.h"
/*
* Intended to be called from bottom-half (e.g. background I/O thread)
* context.
*/
static int do_readdir(V9fsPDU *pdu, V9fsFidState *fidp, struct dirent **dent)
{
int err = 0;
V9fsState *s = pdu->s;
struct dirent *entry;
errno = 0;
entry = s->ops->readdir(&s->ctx, &fidp->fs);
if (!entry && errno) {
*dent = NULL;
err = -errno;
} else {
*dent = entry;
}
return err;
}
/*
* TODO: This will be removed for performance reasons.
* Use v9fs_co_readdir_many() instead.
*/
int coroutine_fn v9fs_co_readdir(V9fsPDU *pdu, V9fsFidState *fidp,
struct dirent **dent)
{
int err;
if (v9fs_request_cancelled(pdu)) {
return -EINTR;
}
v9fs_co_run_in_worker({
err = do_readdir(pdu, fidp, dent);
});
return err;
}
/*
* This is solely executed on a background IO thread.
*
* See v9fs_co_readdir_many() (as its only user) below for details.
*/
static int do_readdir_many(V9fsPDU *pdu, V9fsFidState *fidp,
struct V9fsDirEnt **entries, off_t offset,
int32_t maxsize, bool dostat)
{
V9fsState *s = pdu->s;
V9fsString name;
int len, err = 0;
int32_t size = 0;
off_t saved_dir_pos;
struct dirent *dent;
struct V9fsDirEnt *e = NULL;
V9fsPath path;
struct stat stbuf;
*entries = NULL;
v9fs_path_init(&path);
/*
* TODO: Here should be a warn_report_once() if lock failed.
*
* With a good 9p client we should not get into concurrency here,
* because a good client would not use the same fid for concurrent
* requests. We do the lock here for safety reasons though. However
* the client would then suffer performance issues, so better log that
* issue here.
*/
v9fs_readdir_lock(&fidp->fs.dir);
/* seek directory to requested initial position */
if (offset == 0) {
s->ops->rewinddir(&s->ctx, &fidp->fs);
} else {
s->ops->seekdir(&s->ctx, &fidp->fs, offset);
}
/* save the directory position */
saved_dir_pos = s->ops->telldir(&s->ctx, &fidp->fs);
if (saved_dir_pos < 0) {
err = saved_dir_pos;
goto out;
}
while (true) {
/* interrupt loop if request was cancelled by a Tflush request */
if (v9fs_request_cancelled(pdu)) {
err = -EINTR;
break;
}
/* get directory entry from fs driver */
err = do_readdir(pdu, fidp, &dent);
if (err || !dent) {
break;
}
/*
* stop this loop as soon as it would exceed the allowed maximum
* response message size for the directory entries collected so far,
* because anything beyond that size would need to be discarded by
* 9p controller (main thread / top half) anyway
*/
v9fs_string_init(&name);
v9fs_string_sprintf(&name, "%s", dent->d_name);
len = v9fs_readdir_response_size(&name);
v9fs_string_free(&name);
if (size + len > maxsize) {
/* this is not an error case actually */
break;
}
/* append next node to result chain */
if (!e) {
*entries = e = g_new0(V9fsDirEnt, 1);
} else {
e = e->next = g_new0(V9fsDirEnt, 1);
}
e->dent = qemu_dirent_dup(dent);
/* perform a full stat() for directory entry if requested by caller */
if (dostat) {
err = s->ops->name_to_path(
&s->ctx, &fidp->path, dent->d_name, &path
);
if (err < 0) {
err = -errno;
break;
}
err = s->ops->lstat(&s->ctx, &path, &stbuf);
if (err < 0) {
err = -errno;
break;
}
e->st = g_new0(struct stat, 1);
memcpy(e->st, &stbuf, sizeof(struct stat));
}
size += len;
saved_dir_pos = qemu_dirent_off(dent);
}
/* restore (last) saved position */
s->ops->seekdir(&s->ctx, &fidp->fs, saved_dir_pos);
out:
v9fs_readdir_unlock(&fidp->fs.dir);
v9fs_path_free(&path);
if (err < 0) {
return err;
}
return size;
}
/**
* v9fs_co_readdir_many() - Reads multiple directory entries in one rush.
*
* @pdu: the causing 9p (T_readdir) client request
* @fidp: already opened directory where readdir shall be performed on
* @entries: output for directory entries (must not be NULL)
* @offset: initial position inside the directory the function shall
* seek to before retrieving the directory entries
* @maxsize: maximum result message body size (in bytes)
* @dostat: whether a stat() should be performed and returned for
* each directory entry
* Return: resulting response message body size (in bytes) on success,
* negative error code otherwise
*
* Retrieves the requested (max. amount of) directory entries from the fs
* driver. This function must only be called by the main IO thread (top half).
* Internally this function call will be dispatched to a background IO thread
* (bottom half) where it is eventually executed by the fs driver.
*
* Acquiring multiple directory entries in one rush from the fs
* driver, instead of retrieving each directory entry individually, is very
* beneficial from performance point of view. Because for every fs driver
* request latency is added, which in practice could lead to overall
* latencies of several hundred ms for reading all entries (of just a single
* directory) if every directory entry was individually requested from fs
* driver.
*
* NOTE: You must ALWAYS call v9fs_free_dirents(entries) after calling
* v9fs_co_readdir_many(), both on success and on error cases of this
* function, to avoid memory leaks once @entries are no longer needed.
*/
int coroutine_fn v9fs_co_readdir_many(V9fsPDU *pdu, V9fsFidState *fidp,
struct V9fsDirEnt **entries,
off_t offset, int32_t maxsize,
bool dostat)
{
int err = 0;
if (v9fs_request_cancelled(pdu)) {
return -EINTR;
}
v9fs_co_run_in_worker({
err = do_readdir_many(pdu, fidp, entries, offset, maxsize, dostat);
});
return err;
}
off_t v9fs_co_telldir(V9fsPDU *pdu, V9fsFidState *fidp)
{
off_t err;
V9fsState *s = pdu->s;
if (v9fs_request_cancelled(pdu)) {
return -EINTR;
}
v9fs_co_run_in_worker(
{
err = s->ops->telldir(&s->ctx, &fidp->fs);
if (err < 0) {
err = -errno;
}
});
return err;
}
void coroutine_fn v9fs_co_seekdir(V9fsPDU *pdu, V9fsFidState *fidp,
off_t offset)
{
V9fsState *s = pdu->s;
if (v9fs_request_cancelled(pdu)) {
return;
}
v9fs_co_run_in_worker(
{
s->ops->seekdir(&s->ctx, &fidp->fs, offset);
});
}
void coroutine_fn v9fs_co_rewinddir(V9fsPDU *pdu, V9fsFidState *fidp)
{
V9fsState *s = pdu->s;
if (v9fs_request_cancelled(pdu)) {
return;
}
v9fs_co_run_in_worker(
{
s->ops->rewinddir(&s->ctx, &fidp->fs);
});
}
int coroutine_fn v9fs_co_mkdir(V9fsPDU *pdu, V9fsFidState *fidp,
V9fsString *name, mode_t mode, uid_t uid,
gid_t gid, struct stat *stbuf)
{
int err;
FsCred cred;
V9fsPath path;
V9fsState *s = pdu->s;
if (v9fs_request_cancelled(pdu)) {
return -EINTR;
}
cred_init(&cred);
cred.fc_mode = mode;
cred.fc_uid = uid;
cred.fc_gid = gid;
v9fs_path_read_lock(s);
v9fs_co_run_in_worker(
{
err = s->ops->mkdir(&s->ctx, &fidp->path, name->data, &cred);
if (err < 0) {
err = -errno;
} else {
v9fs_path_init(&path);
err = v9fs_name_to_path(s, &fidp->path, name->data, &path);
if (!err) {
err = s->ops->lstat(&s->ctx, &path, stbuf);
if (err < 0) {
err = -errno;
}
}
v9fs_path_free(&path);
}
});
v9fs_path_unlock(s);
return err;
}
int coroutine_fn v9fs_co_opendir(V9fsPDU *pdu, V9fsFidState *fidp)
{
int err;
V9fsState *s = pdu->s;
if (v9fs_request_cancelled(pdu)) {
return -EINTR;
}
v9fs_path_read_lock(s);
v9fs_co_run_in_worker(
{
err = s->ops->opendir(&s->ctx, &fidp->path, &fidp->fs);
if (err < 0) {
err = -errno;
} else {
err = 0;
}
});
v9fs_path_unlock(s);
if (!err) {
total_open_fd++;
if (total_open_fd > open_fd_hw) {
v9fs_reclaim_fd(pdu);
}
}
return err;
}
int coroutine_fn v9fs_co_closedir(V9fsPDU *pdu, V9fsFidOpenState *fs)
{
int err;
V9fsState *s = pdu->s;
if (v9fs_request_cancelled(pdu)) {
return -EINTR;
}
v9fs_co_run_in_worker(
{
err = s->ops->closedir(&s->ctx, fs);
if (err < 0) {
err = -errno;
}
});
if (!err) {
total_open_fd--;
}
return err;
}