Testing our backing chain handling will make it much easier to
ensure that we avoid issues in the future. If only I had written
this test before I first caused several regressions...
* tests/virstoragetest.c: New test.
* tests/Makefile.am (test_programs): Build it.
* .gitignore: Ignore new files.
---
.gitignore | 1 +
tests/Makefile.am | 6 +
tests/virstoragetest.c | 546 +++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 553 insertions(+)
create mode 100644 tests/virstoragetest.c
diff --git a/.gitignore b/.gitignore
index 8afbf33..23fdc91 100644
--- a/.gitignore
+++ b/.gitignore
@@ -184,6 +184,7 @@
/tests/virnet*test
/tests/virportallocatortest
/tests/virshtest
+/tests/virstoragetest
/tests/virstringtest
/tests/virtimetest
/tests/viruritest
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 7d0a88e..cafdae0 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -100,6 +100,7 @@ test_programs = virshtest sockettest \
virstringtest \
virportallocatortest \
sysinfotest \
+ virstoragetest \
$(NULL)
if WITH_GNUTLS
@@ -565,6 +566,11 @@ virstringtest_SOURCES = \
virstringtest_CFLAGS = -Dabs_builddir="\"$(abs_builddir)\""
$(AM_CFLAGS)
virstringtest_LDADD = $(LDADDS)
+virstoragetest_SOURCES = \
+ virstoragetest.c testutils.h testutils.c
+virstoragetest_CFLAGS = -Dabs_builddir="\"$(abs_builddir)\""
$(AM_CFLAGS)
+virstoragetest_LDADD = $(LDADDS)
+
virlockspacetest_SOURCES = \
virlockspacetest.c testutils.h testutils.c
virlockspacetest_CFLAGS = -Dabs_builddir="\"$(abs_builddir)\""
$(AM_CFLAGS)
diff --git a/tests/virstoragetest.c b/tests/virstoragetest.c
new file mode 100644
index 0000000..6b4ca99
--- /dev/null
+++ b/tests/virstoragetest.c
@@ -0,0 +1,546 @@
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see
+ * <
http://www.gnu.org/licenses/>.
+ *
+ * Author: Eric Blake <eblake(a)redhat.com>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include "testutils.h"
+#include "vircommand.h"
+#include "virerror.h"
+#include "virlog.h"
+#include "virstoragefile.h"
+
+#define VIR_FROM_THIS VIR_FROM_NONE
+
+#define datadir abs_builddir "/virstoragedata"
+
+/* This test creates the following files, all in datadir:
+
+ * raw: 1024-byte raw file
+ * qcow2: qcow2 file with 'raw' as backing
+ * wrap: qcow2 file with 'qcow2' as backing
+ * qed: qed file with 'raw' as backing
+ * sub/link1: symlink to qcow2
+ * sub/link2: symlink to wrap
+ *
+ * Relative names to these files are known at compile time, but absolute
+ * and canonical names depend on where the test is run; for convenience,
+ * we pre-populate the computation of these names for use during the test.
+*/
+
+static char *qemuimg;
+static char *absraw;
+static char *canonraw;
+static char *absqcow2;
+static char *canonqcow2;
+static char *abswrap;
+static char *absqed;
+static char *abslink2;
+
+static void
+testCleanupImages(void)
+{
+ virCommandPtr cmd;
+
+ VIR_FREE(qemuimg);
+ VIR_FREE(absraw);
+ VIR_FREE(canonraw);
+ VIR_FREE(absqcow2);
+ VIR_FREE(canonqcow2);
+ VIR_FREE(abswrap);
+ VIR_FREE(absqed);
+ VIR_FREE(abslink2);
+
+ if (chdir(abs_builddir) < 0) {
+ fprintf(stderr, "unable to return to correct directory, refusing to "
+ "clean up %s\n", datadir);
+ return;
+ }
+
+ cmd = virCommandNewArgList("rm", "-rf", datadir, NULL);
+ ignore_value(virCommandRun(cmd, NULL));
+ virCommandFree(cmd);
+}
+
+static int
+testPrepImages(void)
+{
+ int ret = EXIT_FAILURE;
+ virCommandPtr cmd = NULL;
+
+ qemuimg = virFindFileInPath("kvm-img");
+ if (!qemuimg)
+ qemuimg = virFindFileInPath("qemu-img");
+ if (!qemuimg) {
+ fprintf(stderr, "qemu-img missing or too old; skipping this test\n");
+ return EXIT_AM_SKIP;
+ }
+
+ if (virAsprintf(&absraw, "%s/raw", datadir) < 0 ||
+ virAsprintf(&absqcow2, "%s/qcow2", datadir) < 0 ||
+ virAsprintf(&abswrap, "%s/wrap", datadir) < 0 ||
+ virAsprintf(&absqed, "%s/qed", datadir) < 0 ||
+ virAsprintf(&abslink2, "%s/sub/link2", datadir) < 0) {
+ virReportOOMError();
+ goto cleanup;
+ }
+
+ if (virFileMakePath(datadir "/sub") < 0) {
+ fprintf(stderr, "unable to create directory %s\n", datadir
"/sub");
+ goto cleanup;
+ }
+
+ if (chdir(datadir) < 0) {
+ fprintf(stderr, "unable to test relative backing chains\n");
+ goto cleanup;
+ }
+
+ /* I'm lazy enough to use a shell one-liner instead of open/write/close */
+ virCommandFree(cmd);
+ cmd = virCommandNewArgList("sh", "-c", "printf %1024d 0 >
raw", NULL);
+ if (virCommandRun(cmd, NULL) < 0)
+ goto cleanup;
+ if (!(canonraw = canonicalize_file_name(absraw))) {
+ virReportOOMError();
+ goto cleanup;
+ }
+
+ /* Create a qcow2 wrapping relative raw; later on, we modify its
+ * metadata to test other configurations */
+ virCommandFree(cmd);
+ cmd = virCommandNewArgList("qemu-img", "create", "-f",
"qcow2",
+ "-obacking_file=raw,backing_fmt=raw",
"qcow2",
+ NULL);
+ if (virCommandRun(cmd, NULL) < 0)
+ goto cleanup;
+ /* Make sure our later uses of 'qemu-img rebase' will work */
+ virCommandFree(cmd);
+ cmd = virCommandNewArgList("qemu-img", "rebase", "-u",
"-f", "qcow2",
+ "-F", "raw", "-b",
"raw", "qcow2", NULL);
+ if (virCommandRun(cmd, NULL) < 0) {
+ fprintf(stderr, "qemu-img is too old; skipping this test\n");
+ ret = EXIT_AM_SKIP;
+ goto cleanup;
+ }
+ if (!(canonqcow2 = canonicalize_file_name(absqcow2))) {
+ virReportOOMError();
+ goto cleanup;
+ }
+
+ /* Create a second qcow2 wrapping the first, to be sure that we
+ * can correctly avoid insecure probing. */
+ virCommandFree(cmd);
+ cmd = virCommandNewArgList("qemu-img", "create", "-f",
"qcow2", NULL);
+ virCommandAddArgFormat(cmd, "-obacking_file=%s,backing_fmt=qcow2",
+ absqcow2);
+ virCommandAddArg(cmd, "wrap");
+ if (virCommandRun(cmd, NULL) < 0)
+ goto cleanup;
+
+ /* Create a qed file. */
+ virCommandFree(cmd);
+ cmd = virCommandNewArgList("qemu-img", "create", "-f",
"qed", NULL);
+ virCommandAddArgFormat(cmd, "-obacking_file=%s,backing_fmt=raw",
+ absraw);
+ virCommandAddArg(cmd, "qed");
+ if (virCommandRun(cmd, NULL) < 0)
+ goto cleanup;
+
+ /* Create some symlinks in a sub-directory. */
+ if (symlink("../qcow2", datadir "/sub/link1") < 0 ||
+ symlink("../wrap", datadir "/sub/link2") < 0) {
+ fprintf(stderr, "unable to create symlink");
+ goto cleanup;
+ }
+
+ ret = 0;
+cleanup:
+ virCommandFree(cmd);
+ if (ret)
+ testCleanupImages();
+ return ret;
+}
+
+typedef struct _testFileData testFileData;
+struct _testFileData
+{
+ const char *expBackingStore;
+ const char *expBackingStoreRaw;
+ const char *expDirectory;
+ enum virStorageFileFormat expFormat;
+ bool expIsFile;
+ unsigned long long expCapacity;
+ bool expEncrypted;
+};
+
+enum {
+ EXP_PASS = 0,
+ EXP_FAIL = 1,
+ EXP_WARN = 2,
+ ALLOW_PROBE = 4,
+};
+
+struct testChainData
+{
+ const char *start;
+ enum virStorageFileFormat format;
+ const testFileData *files;
+ int nfiles;
+ unsigned int flags;
+};
+
+static int
+testStorageChain(const void *args)
+{
+ const struct testChainData *data = args;
+ int ret = -1;
+ virStorageFileMetadataPtr meta;
+ virStorageFileMetadataPtr elt;
+ int i = 0;
+
+ meta = virStorageFileGetMetadata(data->start, data->format, -1, -1,
+ (data->flags & ALLOW_PROBE) != 0);
+ if (!meta) {
+ if (data->flags & EXP_FAIL) {
+ virResetLastError();
+ ret = 0;
+ }
+ goto cleanup;
+ } else if (data->flags & EXP_FAIL) {
+ fprintf(stderr, "call should have failed\n");
+ goto cleanup;
+ }
+ if (data->flags & EXP_WARN)
+ virResetLastError();
+
+ elt = meta;
+ while (elt) {
+ char *expect = NULL;
+ char *actual = NULL;
+
+ if (i == data->nfiles) {
+ fprintf(stderr, "probed chain was too long\n");
+ goto cleanup;
+ }
+
+ if (virAsprintf(&expect,
+ "store:%s\nraw:%s\ndirectory:%s\nother:%d %d %lld %d",
+ NULLSTR(data->files[i].expBackingStore),
+ NULLSTR(data->files[i].expBackingStoreRaw),
+ NULLSTR(data->files[i].expDirectory),
+ data->files[i].expFormat,
+ data->files[i].expIsFile,
+ data->files[i].expCapacity,
+ data->files[i].expEncrypted) < 0 ||
+ virAsprintf(&actual,
+ "store:%s\nraw:%s\ndirectory:%s\nother:%d %d %lld %d",
+ NULLSTR(elt->backingStore),
+ NULLSTR(elt->backingStoreRaw),
+ NULLSTR(elt->directory),
+ elt->backingStoreFormat, elt->backingStoreIsFile,
+ elt->capacity, elt->encrypted) < 0) {
+ virReportOOMError();
+ VIR_FREE(expect);
+ VIR_FREE(actual);
+ goto cleanup;
+ }
+ if (STRNEQ(expect, actual)) {
+ virtTestDifference(stderr, expect, actual);
+ VIR_FREE(expect);
+ VIR_FREE(actual);
+ goto cleanup;
+ }
+ VIR_FREE(expect);
+ VIR_FREE(actual);
+ elt = elt->backingMeta;
+ i++;
+ }
+ if (i != data->nfiles) {
+ fprintf(stderr, "probed chain was too short\n");
+ goto cleanup;
+ }
+
+ ret = 0;
+cleanup:
+ virStorageFileFreeMetadata(meta);
+ return ret;
+}
+
+static int
+mymain(void)
+{
+ int ret;
+ virCommandPtr cmd = NULL;
+
+ /* Prep some files with qemu-img; if that is not found on PATH, or
+ * if it lacks support for qcow2 and qed, skip this test. */
+ if ((ret = testPrepImages()) != 0)
+ return ret;
+
+#define TEST_ONE_CHAIN(id, start, format, chain, flags) \
+ do { \
+ struct testChainData data = { \
+ start, format, chain, ARRAY_CARDINALITY(chain), flags, \
+ }; \
+ if (virtTestRun("Storage backing chain " id, 1, \
+ testStorageChain, &data) < 0) \
+ ret = -1; \
+ } while (0)
+
+#define TEST_CHAIN(id, relstart, absstart, format, chain1, flags1, \
+ chain2, flags2, chain3, flags3, chain4, flags4) \
+ do { \
+ TEST_ONE_CHAIN(#id "a", relstart, format, chain1, flags1); \
+ TEST_ONE_CHAIN(#id "b", relstart, format, chain2, flags2); \
+ TEST_ONE_CHAIN(#id "c", absstart, format, chain3, flags3); \
+ TEST_ONE_CHAIN(#id "d", absstart, format, chain4, flags4); \
+ } while (0)
+
+ /* Expected details about files in chains */
+ const testFileData raw = {
+ NULL, NULL, NULL, VIR_STORAGE_FILE_NONE, false, 0, false,
+ };
+ const testFileData qcow2_relback_relstart = {
+ canonraw, "raw", ".", VIR_STORAGE_FILE_RAW, true, 1024,
false,
+ };
+ const testFileData qcow2_relback_absstart = {
+ canonraw, "raw", datadir, VIR_STORAGE_FILE_RAW, true, 1024, false,
+ };
+ const testFileData qcow2_absback = {
+ canonraw, absraw, datadir, VIR_STORAGE_FILE_RAW, true, 1024, false,
+ };
+ const testFileData qcow2_as_probe = {
+ canonraw, absraw, datadir, VIR_STORAGE_FILE_AUTO, true, 1024, false,
+ };
+ const testFileData qcow2_bogus = {
+ NULL, datadir "/bogus", datadir, VIR_STORAGE_FILE_NONE,
+ false, 1024, false,
+ };
+ const testFileData qcow2_protocol = {
+ "nbd:example.org:6000", NULL, NULL, VIR_STORAGE_FILE_RAW,
+ false, 1024, false,
+ };
+ const testFileData wrap = {
+ canonqcow2, absqcow2, datadir, VIR_STORAGE_FILE_QCOW2,
+ true, 1024, false,
+ };
+ const testFileData wrap_as_raw = {
+ canonqcow2, absqcow2, datadir, VIR_STORAGE_FILE_RAW,
+ true, 1024, false,
+ };
+ const testFileData wrap_as_probe = {
+ canonqcow2, absqcow2, datadir, VIR_STORAGE_FILE_AUTO,
+ true, 1024, false,
+ };
+ const testFileData qed = {
+ canonraw, absraw, datadir, VIR_STORAGE_FILE_RAW,
+ true, 1024, false,
+ };
+ const testFileData link1_rel = {
+ canonraw, "../raw", "sub/../sub/..", VIR_STORAGE_FILE_RAW,
+ true, 1024, false,
+ };
+ const testFileData link1_abs = {
+ canonraw, "../raw", datadir "/sub/../sub/..",
VIR_STORAGE_FILE_RAW,
+ true, 1024, false,
+ };
+ const testFileData link2_rel = {
+ canonqcow2, "../sub/link1", "sub/../sub",
VIR_STORAGE_FILE_QCOW2,
+ true, 1024, false,
+ };
+ const testFileData link2_abs = {
+ canonqcow2, "../sub/link1", datadir "/sub/../sub",
+ VIR_STORAGE_FILE_QCOW2, true, 1024, false,
+ };
+
+ /* The actual tests, in several groups. */
+
+ /* Missing file */
+ const testFileData chain0[] = { };
+ TEST_ONE_CHAIN("0", "bogus", VIR_STORAGE_FILE_RAW, chain0,
EXP_FAIL);
+
+ /* Raw image, whether with right format or no specified format */
+ const testFileData chain1[] = { raw };
+ TEST_CHAIN(1, "raw", absraw, VIR_STORAGE_FILE_RAW,
+ chain1, EXP_PASS,
+ chain1, ALLOW_PROBE | EXP_PASS,
+ chain1, EXP_PASS,
+ chain1, ALLOW_PROBE | EXP_PASS);
+ TEST_CHAIN(2, "raw", absraw, VIR_STORAGE_FILE_AUTO,
+ chain1, EXP_PASS,
+ chain1, ALLOW_PROBE | EXP_PASS,
+ chain1, EXP_PASS,
+ chain1, ALLOW_PROBE | EXP_PASS);
+
+ /* Qcow2 file with relative raw backing, format provided */
+ const testFileData chain3a[] = { qcow2_relback_relstart, raw };
+ const testFileData chain3c[] = { qcow2_relback_absstart, raw };
+ const testFileData chain4a[] = { raw };
+ TEST_CHAIN(3, "qcow2", absqcow2, VIR_STORAGE_FILE_QCOW2,
+ chain3a, EXP_PASS,
+ chain3a, ALLOW_PROBE | EXP_PASS,
+ chain3c, EXP_PASS,
+ chain3c, ALLOW_PROBE | EXP_PASS);
+ TEST_CHAIN(4, "qcow2", absqcow2, VIR_STORAGE_FILE_AUTO,
+ chain4a, EXP_PASS,
+ chain3a, ALLOW_PROBE | EXP_PASS,
+ chain4a, EXP_PASS,
+ chain3c, ALLOW_PROBE | EXP_PASS);
+
+ /* Rewrite qcow2 file to use absolute backing name */
+ virCommandFree(cmd);
+ cmd = virCommandNewArgList(qemuimg, "rebase", "-u",
"-f", "qcow2",
+ "-F", "raw", "-b", absraw,
"qcow2", NULL);
+ if (virCommandRun(cmd, NULL) < 0)
+ ret = -1;
+
+ /* Qcow2 file with raw as absolute backing, backing format provided */
+ const testFileData chain5[] = { qcow2_absback, raw };
+ const testFileData chain6[] = { raw };
+ TEST_CHAIN(5, "qcow2", absqcow2, VIR_STORAGE_FILE_QCOW2,
+ chain5, EXP_PASS,
+ chain5, ALLOW_PROBE | EXP_PASS,
+ chain5, EXP_PASS,
+ chain5, ALLOW_PROBE | EXP_PASS);
+ TEST_CHAIN(6, "qcow2", absqcow2, VIR_STORAGE_FILE_AUTO,
+ chain6, EXP_PASS,
+ chain5, ALLOW_PROBE | EXP_PASS,
+ chain6, EXP_PASS,
+ chain5, ALLOW_PROBE | EXP_PASS);
+
+ /* Wrapped file access */
+ const testFileData chain7[] = { wrap, qcow2_absback, raw };
+ TEST_CHAIN(7, "wrap", abswrap, VIR_STORAGE_FILE_QCOW2,
+ chain7, EXP_PASS,
+ chain7, ALLOW_PROBE | EXP_PASS,
+ chain7, EXP_PASS,
+ chain7, ALLOW_PROBE | EXP_PASS);
+
+ /* Rewrite qcow2 and wrap file to omit backing file type */
+ virCommandFree(cmd);
+ cmd = virCommandNewArgList(qemuimg, "rebase", "-u",
"-f", "qcow2",
+ "-b", absraw, "qcow2", NULL);
+ if (virCommandRun(cmd, NULL) < 0)
+ ret = -1;
+
+ virCommandFree(cmd);
+ cmd = virCommandNewArgList(qemuimg, "rebase", "-u",
"-f", "qcow2",
+ "-b", absqcow2, "wrap", NULL);
+ if (virCommandRun(cmd, NULL) < 0)
+ ret = -1;
+
+ /* Qcow2 file with raw as absolute backing, backing format omitted */
+ const testFileData chain8a[] = { wrap_as_raw, raw };
+ const testFileData chain8b[] = { wrap_as_probe, qcow2_as_probe, raw };
+ TEST_CHAIN(8, "wrap", abswrap, VIR_STORAGE_FILE_QCOW2,
+ chain8a, EXP_PASS,
+ chain8b, ALLOW_PROBE | EXP_PASS,
+ chain8a, EXP_PASS,
+ chain8b, ALLOW_PROBE | EXP_PASS);
+
+ /* Rewrite qcow2 to a missing backing file, with backing type */
+ virCommandFree(cmd);
+ cmd = virCommandNewArgList(qemuimg, "rebase", "-u",
"-f", "qcow2",
+ "-F", "qcow2", "-b", datadir
"/bogus",
+ "qcow2", NULL);
+ if (virCommandRun(cmd, NULL) < 0)
+ ret = -1;
+
+ /* Qcow2 file with missing backing file but specified type */
+ const testFileData chain9[] = { qcow2_bogus };
+ TEST_CHAIN(9, "qcow2", absqcow2, VIR_STORAGE_FILE_QCOW2,
+ chain9, EXP_WARN,
+ chain9, ALLOW_PROBE | EXP_WARN,
+ chain9, EXP_WARN,
+ chain9, ALLOW_PROBE | EXP_WARN);
+
+ /* Rewrite qcow2 to a missing backing file, without backing type */
+ virCommandFree(cmd);
+ cmd = virCommandNewArgList(qemuimg, "rebase", "-u",
"-f", "qcow2",
+ "-b", datadir "/bogus",
"qcow2", NULL);
+ if (virCommandRun(cmd, NULL) < 0)
+ ret = -1;
+
+ /* Qcow2 file with missing backing file and no specified type */
+ const testFileData chain10[] = { qcow2_bogus };
+ TEST_CHAIN(10, "qcow2", absqcow2, VIR_STORAGE_FILE_QCOW2,
+ chain10, EXP_WARN,
+ chain10, ALLOW_PROBE | EXP_WARN,
+ chain10, EXP_WARN,
+ chain10, ALLOW_PROBE | EXP_WARN);
+
+ /* Rewrite qcow2 to use an nbd: protocol as backend */
+ virCommandFree(cmd);
+ cmd = virCommandNewArgList(qemuimg, "rebase", "-u",
"-f", "qcow2",
+ "-F", "raw", "-b",
"nbd:example.org:6000",
+ "qcow2", NULL);
+ if (virCommandRun(cmd, NULL) < 0)
+ ret = -1;
+
+ /* Qcow2 file with backing protocol instead of file */
+ const testFileData chain11[] = { qcow2_protocol };
+ TEST_CHAIN(11, "qcow2", absqcow2, VIR_STORAGE_FILE_QCOW2,
+ chain11, EXP_WARN,
+ chain11, ALLOW_PROBE | EXP_WARN,
+ chain11, EXP_WARN,
+ chain11, ALLOW_PROBE | EXP_WARN);
+
+ /* qed file */
+ const testFileData chain12a[] = { raw };
+ const testFileData chain12b[] = { qed, raw };
+ TEST_CHAIN(12, "qed", absqed, VIR_STORAGE_FILE_AUTO,
+ chain12a, EXP_PASS,
+ chain12b, ALLOW_PROBE | EXP_PASS,
+ chain12a, EXP_PASS,
+ chain12b, ALLOW_PROBE | EXP_PASS);
+
+ /* Rewrite qcow2 and wrap file to use backing names relative to a
+ * symlink from a different directory */
+ virCommandFree(cmd);
+ cmd = virCommandNewArgList(qemuimg, "rebase", "-u",
"-f", "qcow2",
+ "-F", "raw", "-b",
"../raw", "qcow2", NULL);
+ if (virCommandRun(cmd, NULL) < 0)
+ ret = -1;
+
+ virCommandFree(cmd);
+ cmd = virCommandNewArgList(qemuimg, "rebase", "-u",
"-f", "qcow2",
+ "-F", "qcow2", "-b",
"../sub/link1", "wrap",
+ NULL);
+ if (virCommandRun(cmd, NULL) < 0)
+ ret = -1;
+
+ /* Behavior of symlinks to qcow2 with relative backing files */
+ const testFileData chain13a[] = { link2_rel, link1_rel, raw };
+ const testFileData chain13c[] = { link2_abs, link1_abs, raw };
+ TEST_CHAIN(13, "sub/link2", abslink2, VIR_STORAGE_FILE_QCOW2,
+ chain13a, EXP_PASS,
+ chain13a, ALLOW_PROBE | EXP_PASS,
+ chain13c, EXP_PASS,
+ chain13c, ALLOW_PROBE | EXP_PASS);
+
+ /* Final cleanup */
+ testCleanupImages();
+ virCommandFree(cmd);
+
+ return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+VIRT_TEST_MAIN(mymain)
--
1.8.1.2