9pfs: add tests using local fs driver

The currently existing 9pfs test cases are all solely using the 9pfs 'synth'
 fileystem driver, which is a very simple and purely simulated (in RAM only)
 filesystem. There are issues though where the 'synth' fs driver is not
 sufficient. For example the following two bugs need test cases running the
 9pfs 'local' fs driver:
 
 https://bugs.launchpad.net/qemu/+bug/1336794
 https://bugs.launchpad.net/qemu/+bug/1877384
 
 This patch set for that reason introduces 9pfs test cases using the 9pfs
 'local' filesystem driver along to the already existing tests on 'synth'.
 -----BEGIN PGP SIGNATURE-----
 
 iQJLBAABCgA1FiEEltjREM96+AhPiFkBNMK1h2Wkc5UFAl+NiOwXHHFlbXVfb3Nz
 QGNydWRlYnl0ZS5jb20ACgkQNMK1h2Wkc5VkIw//fP5v3Wx8PUk03Vega7aDjtFN
 fpLG1hpyhv/aEYijSrvxNdwuEfT7eWae3yFnc3uEXAv+61sHCuzqsQVRYPi3mO6i
 9o2072shPQq5LDgYd8kSyqSOox/06r+9dIqrIFoLh9POYDN25fJGHHmQETXhXaCw
 /Vuhm0WvMryEJmuVS7kF4f9znX6dT4P9xgHpVod1tbIAB/vCKOFAqOWiNWS0vNdF
 OP0nYQWoYNey9TfX7ZJt1b8av0jLlYfDTcooojQKT70ihMqUSCdOvOIdlMbJj3pU
 P/z28xYwAQT1WxLCzV6DskNYCSTsxVakO4lW6JXPLSL2e4TQlzx2lwlUFGzuKKsg
 Tmgy5xIB1dXa0LMfjNJ5hQHawcCNQbIYLh2Jw3Qx4Q48SAvi4el9leUtrpXlYDPG
 nQ0iuwoaFGd4BHoRk9Q48AVlOj7XelkhsEpREmjXESDgdf2CTw7pQOkuc6Rx/9S6
 GjjjWNN4f7MjcfBkQhcmrHVMOWJGrBUn19kgdY4/suqmcR68Tqq56OHLAF9kL55+
 mWntjT3GBjML7IW3cWxLPSGvLzE3ydwCrJY8pvzZySJmVGwgeXQ5o25LtwWElmcj
 Jf1zXeHQM8+g5qEkpdt1zKH2CrRfkCUofp8N4fP9tFCMmaYS3/oLEDgzGp5dUFIU
 nUdUTdEUm/arxRFLd/8=
 =TZ2q
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/cschoenebeck/tags/pull-9p-20201019' into staging

9pfs: add tests using local fs driver

The currently existing 9pfs test cases are all solely using the 9pfs 'synth'
fileystem driver, which is a very simple and purely simulated (in RAM only)
filesystem. There are issues though where the 'synth' fs driver is not
sufficient. For example the following two bugs need test cases running the
9pfs 'local' fs driver:

https://bugs.launchpad.net/qemu/+bug/1336794
https://bugs.launchpad.net/qemu/+bug/1877384

This patch set for that reason introduces 9pfs test cases using the 9pfs
'local' filesystem driver along to the already existing tests on 'synth'.

# gpg: Signature made Mon 19 Oct 2020 13:39:08 BST
# gpg:                using RSA key 96D8D110CF7AF8084F88590134C2B58765A47395
# gpg:                issuer "qemu_oss@crudebyte.com"
# gpg: Good signature from "Christian Schoenebeck <qemu_oss@crudebyte.com>" [unknown]
# gpg: WARNING: This key 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

* remotes/cschoenebeck/tags/pull-9p-20201019:
  tests/9pfs: add local Tmkdir test
  tests/9pfs: add virtio_9p_test_path()
  tests/9pfs: wipe local 9pfs test directory
  tests/9pfs: introduce local tests
  tests/9pfs: change qtest name prefix to synth
  9pfs: suppress performance warnings on qtest runs

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2020-10-19 14:39:26 +01:00
commit d76f4f97eb
5 changed files with 292 additions and 23 deletions

View File

@ -541,8 +541,6 @@ static int synth_init(FsContext *ctx, Error **errp)
QLIST_INIT(&synth_root.child);
qemu_mutex_init(&synth_mutex);
ctx->export_flags |= V9FS_NO_PERF_WARN;
/* Add "." and ".." entries for root */
v9fs_add_dir_node(&synth_root, synth_root.attr->mode,
"..", synth_root.attr, synth_root.attr->inode);

View File

@ -21,6 +21,7 @@
#include "hw/virtio/virtio-access.h"
#include "qemu/iov.h"
#include "qemu/module.h"
#include "sysemu/qtest.h"
static void virtio_9p_push_and_notify(V9fsPDU *pdu)
{
@ -199,6 +200,11 @@ static void virtio_9p_device_realize(DeviceState *dev, Error **errp)
VirtIODevice *vdev = VIRTIO_DEVICE(dev);
V9fsVirtioState *v = VIRTIO_9P(dev);
V9fsState *s = &v->state;
FsDriverEntry *fse = get_fsdev_fsentry(s->fsconf.fsdev_id);
if (qtest_enabled() && fse) {
fse->export_flags |= V9FS_NO_PERF_WARN;
}
if (v9fs_device_realize_common(s, &virtio_9p_transport, errp)) {
return;

View File

@ -24,6 +24,52 @@
#include "qgraph.h"
static QGuestAllocator *alloc;
static char *local_test_path;
/* Concatenates the passed 2 pathes. Returned result must be freed. */
static char *concat_path(const char* a, const char* b)
{
return g_build_filename(a, b, NULL);
}
static void init_local_test_path(void)
{
char *pwd = g_get_current_dir();
local_test_path = concat_path(pwd, "qtest-9p-local");
g_free(pwd);
}
/* Creates the directory for the 9pfs 'local' filesystem driver to access. */
static void create_local_test_dir(void)
{
struct stat st;
g_assert(local_test_path != NULL);
mkdir(local_test_path, 0777);
/* ensure test directory exists now ... */
g_assert(stat(local_test_path, &st) == 0);
/* ... and is actually a directory */
g_assert((st.st_mode & S_IFMT) == S_IFDIR);
}
/* Deletes directory previously created by create_local_test_dir(). */
static void remove_local_test_dir(void)
{
g_assert(local_test_path != NULL);
char *cmd = g_strdup_printf("rm -r '%s'\n", local_test_path);
int res = system(cmd);
if (res < 0) {
/* ignore error, dummy check to prevent compiler error */
}
g_free(cmd);
}
char *virtio_9p_test_path(const char *path)
{
g_assert(local_test_path);
return concat_path(local_test_path, path);
}
static void virtio_9p_cleanup(QVirtio9P *interface)
{
@ -146,11 +192,65 @@ static void *virtio_9p_pci_create(void *pci_bus, QGuestAllocator *t_alloc,
return obj;
}
/**
* Performs regular expression based search and replace on @a haystack.
*
* @param haystack - input string to be parsed, result of replacement is
* stored back to @a haystack
* @param pattern - the regular expression pattern for scanning @a haystack
* @param replace_fmt - matches of supplied @a pattern are replaced by this,
* if necessary glib printf format can be used to add
* variable arguments of this function to this
* replacement string
*/
static void regex_replace(GString *haystack, const char *pattern,
const char *replace_fmt, ...)
{
GRegex *regex;
char *replace, *s;
va_list argp;
va_start(argp, replace_fmt);
replace = g_strdup_vprintf(replace_fmt, argp);
va_end(argp);
regex = g_regex_new(pattern, 0, 0, NULL);
s = g_regex_replace(regex, haystack->str, -1, 0, replace, 0, NULL);
g_string_assign(haystack, s);
g_free(s);
g_regex_unref(regex);
g_free(replace);
}
void virtio_9p_assign_local_driver(GString *cmd_line, const char *args)
{
g_assert_nonnull(local_test_path);
/* replace 'synth' driver by 'local' driver */
regex_replace(cmd_line, "-fsdev synth,", "-fsdev local,");
/* append 'path=...' to '-fsdev ...' group */
regex_replace(cmd_line, "(-fsdev \\w[^ ]*)", "\\1,path='%s'",
local_test_path);
if (!args) {
return;
}
/* append passed args to '-fsdev ...' group */
regex_replace(cmd_line, "(-fsdev \\w[^ ]*)", "\\1,%s", args);
}
static void virtio_9p_register_nodes(void)
{
const char *str_simple = "fsdev=fsdev0,mount_tag=" MOUNT_TAG;
const char *str_addr = "fsdev=fsdev0,addr=04.0,mount_tag=" MOUNT_TAG;
/* make sure test dir for the 'local' tests exists and is clean */
init_local_test_path();
remove_local_test_dir();
create_local_test_dir();
QPCIAddress addr = {
.devfn = QPCI_DEVFN(4, 0),
};

View File

@ -44,4 +44,14 @@ struct QVirtio9PDevice {
QVirtio9P v9p;
};
/**
* Prepares QEMU command line for 9pfs tests using the 'local' fs driver.
*/
void virtio_9p_assign_local_driver(GString *cmd_line, const char *args);
/**
* Returns path on host to the passed guest path. Result must be freed.
*/
char *virtio_9p_test_path(const char *path);
#endif

View File

@ -18,6 +18,62 @@
#define QVIRTIO_9P_TIMEOUT_US (10 * 1000 * 1000)
static QGuestAllocator *alloc;
/*
* Used to auto generate new fids. Start with arbitrary high value to avoid
* collision with hard coded fids in basic test code.
*/
static uint32_t fid_generator = 1000;
static uint32_t genfid(void)
{
return fid_generator++;
}
/**
* Splits the @a in string by @a delim into individual (non empty) strings
* and outputs them to @a out. The output array @a out is NULL terminated.
*
* Output array @a out must be freed by calling split_free().
*
* @returns number of individual elements in output array @a out (without the
* final NULL terminating element)
*/
static int split(const char *in, const char *delim, char ***out)
{
int n = 0, i = 0;
char *tmp, *p;
tmp = g_strdup(in);
for (p = strtok(tmp, delim); p != NULL; p = strtok(NULL, delim)) {
if (strlen(p) > 0) {
++n;
}
}
g_free(tmp);
*out = g_new0(char *, n + 1); /* last element NULL delimiter */
tmp = g_strdup(in);
for (p = strtok(tmp, delim); p != NULL; p = strtok(NULL, delim)) {
if (strlen(p) > 0) {
(*out)[i++] = g_strdup(p);
}
}
g_free(tmp);
return n;
}
static void split_free(char ***out)
{
int i;
for (i = 0; (*out)[i]; ++i) {
g_free((*out)[i]);
}
g_free(*out);
*out = NULL;
}
static void pci_config(void *obj, void *data, QGuestAllocator *t_alloc)
{
QVirtio9P *v9p = obj;
@ -201,6 +257,7 @@ static const char *rmessage_name(uint8_t id)
id == P9_RWALK ? "RWALK" :
id == P9_RLOPEN ? "RLOPEN" :
id == P9_RWRITE ? "RWRITE" :
id == P9_RMKDIR ? "RMKDIR" :
id == P9_RFLUSH ? "RFLUSH" :
id == P9_RREADDIR ? "READDIR" :
"<unknown>";
@ -578,6 +635,39 @@ static bool fs_dirents_contain_name(struct V9fsDirent *e, const char* name)
return false;
}
/* size[4] Tmkdir tag[2] dfid[4] name[s] mode[4] gid[4] */
static P9Req *v9fs_tmkdir(QVirtio9P *v9p, uint32_t dfid, const char *name,
uint32_t mode, uint32_t gid, uint16_t tag)
{
P9Req *req;
uint32_t body_size = 4 + 4 + 4;
uint16_t string_size = v9fs_string_size(name);
g_assert_cmpint(body_size, <=, UINT32_MAX - string_size);
body_size += string_size;
req = v9fs_req_init(v9p, body_size, P9_TMKDIR, tag);
v9fs_uint32_write(req, dfid);
v9fs_string_write(req, name);
v9fs_uint32_write(req, mode);
v9fs_uint32_write(req, gid);
v9fs_req_send(req);
return req;
}
/* size[4] Rmkdir tag[2] qid[13] */
static void v9fs_rmkdir(P9Req *req, v9fs_qid *qid)
{
v9fs_req_recv(req, P9_RMKDIR);
if (qid) {
v9fs_memread(req, qid, 13);
} else {
v9fs_memskip(req, 13);
}
v9fs_req_free(req);
}
/* basic readdir test where reply fits into a single response message */
static void fs_readdir(void *obj, void *data, QGuestAllocator *t_alloc)
{
@ -877,6 +967,30 @@ static void fs_flush_ignored(void *obj, void *data, QGuestAllocator *t_alloc)
g_free(wnames[0]);
}
static void fs_mkdir(void *obj, void *data, QGuestAllocator *t_alloc,
const char *path, const char *cname)
{
QVirtio9P *v9p = obj;
alloc = t_alloc;
char **wnames;
char *const name = g_strdup(cname);
P9Req *req;
const uint32_t fid = genfid();
int nwnames = split(path, "/", &wnames);
req = v9fs_twalk(v9p, 0, fid, nwnames, wnames, 0);
v9fs_req_wait_for_reply(req, NULL);
v9fs_rwalk(req, NULL, NULL);
req = v9fs_tmkdir(v9p, fid, name, 0750, 0, 0);
v9fs_req_wait_for_reply(req, NULL);
v9fs_rmkdir(req, NULL);
g_free(name);
split_free(&wnames);
}
static void fs_readdir_split_128(void *obj, void *data,
QGuestAllocator *t_alloc)
{
@ -895,29 +1009,70 @@ static void fs_readdir_split_512(void *obj, void *data,
fs_readdir_split(obj, data, t_alloc, 512);
}
/* tests using the 9pfs 'local' fs driver */
static void fs_create_dir(void *obj, void *data, QGuestAllocator *t_alloc)
{
QVirtio9P *v9p = obj;
struct stat st;
char *root_path = virtio_9p_test_path("");
char *new_dir = virtio_9p_test_path("01");
g_assert(root_path != NULL);
fs_attach(v9p, NULL, t_alloc);
fs_mkdir(v9p, data, t_alloc, "/", "01");
/* check if created directory really exists now ... */
g_assert(stat(new_dir, &st) == 0);
/* ... and is actually a directory */
g_assert((st.st_mode & S_IFMT) == S_IFDIR);
g_free(new_dir);
g_free(root_path);
}
static void *assign_9p_local_driver(GString *cmd_line, void *arg)
{
virtio_9p_assign_local_driver(cmd_line, "security_model=mapped-xattr");
return arg;
}
static void register_virtio_9p_test(void)
{
qos_add_test("config", "virtio-9p", pci_config, NULL);
qos_add_test("fs/version/basic", "virtio-9p", fs_version, NULL);
qos_add_test("fs/attach/basic", "virtio-9p", fs_attach, NULL);
qos_add_test("fs/walk/basic", "virtio-9p", fs_walk, NULL);
qos_add_test("fs/walk/no_slash", "virtio-9p", fs_walk_no_slash,
NULL);
qos_add_test("fs/walk/dotdot_from_root", "virtio-9p",
fs_walk_dotdot, NULL);
qos_add_test("fs/lopen/basic", "virtio-9p", fs_lopen, NULL);
qos_add_test("fs/write/basic", "virtio-9p", fs_write, NULL);
qos_add_test("fs/flush/success", "virtio-9p", fs_flush_success,
NULL);
qos_add_test("fs/flush/ignored", "virtio-9p", fs_flush_ignored,
NULL);
qos_add_test("fs/readdir/basic", "virtio-9p", fs_readdir, NULL);
qos_add_test("fs/readdir/split_512", "virtio-9p",
fs_readdir_split_512, NULL);
qos_add_test("fs/readdir/split_256", "virtio-9p",
fs_readdir_split_256, NULL);
qos_add_test("fs/readdir/split_128", "virtio-9p",
fs_readdir_split_128, NULL);
QOSGraphTestOptions opts = {
};
/* 9pfs test cases using the 'synth' filesystem driver */
qos_add_test("synth/config", "virtio-9p", pci_config, &opts);
qos_add_test("synth/version/basic", "virtio-9p", fs_version, &opts);
qos_add_test("synth/attach/basic", "virtio-9p", fs_attach, &opts);
qos_add_test("synth/walk/basic", "virtio-9p", fs_walk, &opts);
qos_add_test("synth/walk/no_slash", "virtio-9p", fs_walk_no_slash,
&opts);
qos_add_test("synth/walk/dotdot_from_root", "virtio-9p",
fs_walk_dotdot, &opts);
qos_add_test("synth/lopen/basic", "virtio-9p", fs_lopen, &opts);
qos_add_test("synth/write/basic", "virtio-9p", fs_write, &opts);
qos_add_test("synth/flush/success", "virtio-9p", fs_flush_success,
&opts);
qos_add_test("synth/flush/ignored", "virtio-9p", fs_flush_ignored,
&opts);
qos_add_test("synth/readdir/basic", "virtio-9p", fs_readdir, &opts);
qos_add_test("synth/readdir/split_512", "virtio-9p",
fs_readdir_split_512, &opts);
qos_add_test("synth/readdir/split_256", "virtio-9p",
fs_readdir_split_256, &opts);
qos_add_test("synth/readdir/split_128", "virtio-9p",
fs_readdir_split_128, &opts);
/* 9pfs test cases using the 'local' filesystem driver */
opts.before = assign_9p_local_driver;
qos_add_test("local/config", "virtio-9p", pci_config, &opts);
qos_add_test("local/create_dir", "virtio-9p", fs_create_dir, &opts);
}
libqos_init(register_virtio_9p_test);