#include #ifdef WITH_QEMU # include "testutilsqemu.h" # include "testutilshostcpus.h" # include "testutils.h" # include "viralloc.h" # include "cpu_conf.h" # include "qemu/qemu_driver.h" # include "qemu/qemu_domain.h" # define LIBVIRT_QEMU_CAPSPRIV_H_ALLOW # include "qemu/qemu_capspriv.h" # include "virstring.h" # include "virfilecache.h" # define VIR_FROM_THIS VIR_FROM_QEMU virCPUDefPtr cpuDefault; virCPUDefPtr cpuHaswell; virCPUDefPtr cpuPower8; virCPUDefPtr cpuPower9; typedef enum { TEST_UTILS_QEMU_BIN_I686, TEST_UTILS_QEMU_BIN_X86_64, TEST_UTILS_QEMU_BIN_AARCH64, TEST_UTILS_QEMU_BIN_ARM, TEST_UTILS_QEMU_BIN_PPC64, TEST_UTILS_QEMU_BIN_PPC, TEST_UTILS_QEMU_BIN_RISCV32, TEST_UTILS_QEMU_BIN_RISCV64, TEST_UTILS_QEMU_BIN_S390X } QEMUBinType; static const char *QEMUBinList[] = { "/usr/bin/qemu-system-i686", "/usr/bin/qemu-system-x86_64", "/usr/bin/qemu-system-aarch64", "/usr/bin/qemu-system-arm", "/usr/bin/qemu-system-ppc64", "/usr/bin/qemu-system-ppc", "/usr/bin/qemu-system-riscv32", "/usr/bin/qemu-system-riscv64", "/usr/bin/qemu-system-s390x" }; static virCapsGuestMachinePtr *testQemuAllocMachines(int *nmachines) { virCapsGuestMachinePtr *machines; static const char *const x86_machines[] = { "pc", "isapc" }; machines = virCapabilitiesAllocMachines(x86_machines, ARRAY_CARDINALITY(x86_machines)); if (machines == NULL) return NULL; *nmachines = ARRAY_CARDINALITY(x86_machines); return machines; } /* Newer versions of qemu have versioned machine types to allow * compatibility with older releases. * The 'pc' machine type is an alias of the newest machine type. */ static virCapsGuestMachinePtr *testQemuAllocNewerMachines(int *nmachines) { virCapsGuestMachinePtr *machines; char *canonical; static const char *const x86_machines[] = { "pc-0.11", "pc", "pc-0.10", "isapc" }; if (VIR_STRDUP(canonical, x86_machines[0]) < 0) return NULL; machines = virCapabilitiesAllocMachines(x86_machines, ARRAY_CARDINALITY(x86_machines)); if (machines == NULL) { VIR_FREE(canonical); return NULL; } machines[1]->canonical = canonical; *nmachines = ARRAY_CARDINALITY(x86_machines); return machines; } static int testQemuAddI686Guest(virCapsPtr caps) { int nmachines = 0; virCapsGuestMachinePtr *machines = NULL; virCapsGuestPtr guest; if (!(machines = testQemuAllocMachines(&nmachines))) goto error; if (!(guest = virCapabilitiesAddGuest(caps, VIR_DOMAIN_OSTYPE_HVM, VIR_ARCH_I686, QEMUBinList[TEST_UTILS_QEMU_BIN_I686], NULL, nmachines, machines))) goto error; if (!virCapabilitiesAddGuestFeature(guest, "cpuselection", true, false)) goto error; machines = NULL; if (!virCapabilitiesAddGuestDomain(guest, VIR_DOMAIN_VIRT_QEMU, NULL, NULL, 0, NULL)) goto error; if (!(machines = testQemuAllocMachines(&nmachines))) goto error; if (!virCapabilitiesAddGuestDomain(guest, VIR_DOMAIN_VIRT_KVM, QEMUBinList[TEST_UTILS_QEMU_BIN_I686], NULL, nmachines, machines)) goto error; return 0; error: virCapabilitiesFreeMachines(machines, nmachines); return -1; } static int testQemuAddX86_64Guest(virCapsPtr caps) { int nmachines = 0; virCapsGuestMachinePtr *machines = NULL; virCapsGuestPtr guest; if (!(machines = testQemuAllocNewerMachines(&nmachines))) goto error; if (!(guest = virCapabilitiesAddGuest(caps, VIR_DOMAIN_OSTYPE_HVM, VIR_ARCH_X86_64, QEMUBinList[TEST_UTILS_QEMU_BIN_X86_64], NULL, nmachines, machines))) goto error; if (!virCapabilitiesAddGuestFeature(guest, "cpuselection", true, false)) goto error; machines = NULL; if (!virCapabilitiesAddGuestDomain(guest, VIR_DOMAIN_VIRT_QEMU, NULL, NULL, 0, NULL)) goto error; if (!(machines = testQemuAllocMachines(&nmachines))) goto error; if (!virCapabilitiesAddGuestDomain(guest, VIR_DOMAIN_VIRT_KVM, QEMUBinList[TEST_UTILS_QEMU_BIN_X86_64], NULL, nmachines, machines)) goto error; machines = NULL; if (!virCapabilitiesAddGuestDomain(guest, VIR_DOMAIN_VIRT_KVM, QEMUBinList[TEST_UTILS_QEMU_BIN_X86_64], NULL, 0, NULL)) goto error; return 0; error: virCapabilitiesFreeMachines(machines, nmachines); return -1; } static int testQemuAddPPC64Guest(virCapsPtr caps) { static const char *machine[] = { "pseries" }; virCapsGuestMachinePtr *machines = NULL; virCapsGuestPtr guest; machines = virCapabilitiesAllocMachines(machine, 1); if (!machines) goto error; guest = virCapabilitiesAddGuest(caps, VIR_DOMAIN_OSTYPE_HVM, VIR_ARCH_PPC64, QEMUBinList[TEST_UTILS_QEMU_BIN_PPC64], NULL, 1, machines); if (!guest) goto error; if (!virCapabilitiesAddGuestDomain(guest, VIR_DOMAIN_VIRT_QEMU, NULL, NULL, 0, NULL)) goto error; if (!virCapabilitiesAddGuestDomain(guest, VIR_DOMAIN_VIRT_KVM, NULL, NULL, 0, NULL)) goto error; return 0; error: /* No way to free a guest? */ virCapabilitiesFreeMachines(machines, 1); return -1; } static int testQemuAddPPC64LEGuest(virCapsPtr caps) { static const char *machine[] = { "pseries" }; virCapsGuestMachinePtr *machines = NULL; virCapsGuestPtr guest; machines = virCapabilitiesAllocMachines(machine, 1); if (!machines) goto error; guest = virCapabilitiesAddGuest(caps, VIR_DOMAIN_OSTYPE_HVM, VIR_ARCH_PPC64LE, QEMUBinList[TEST_UTILS_QEMU_BIN_PPC64], NULL, 1, machines); if (!guest) goto error; if (!virCapabilitiesAddGuestDomain(guest, VIR_DOMAIN_VIRT_QEMU, NULL, NULL, 0, NULL)) goto error; if (!virCapabilitiesAddGuestDomain(guest, VIR_DOMAIN_VIRT_KVM, NULL, NULL, 0, NULL)) goto error; return 0; error: /* No way to free a guest? */ virCapabilitiesFreeMachines(machines, 1); return -1; } static int testQemuAddPPCGuest(virCapsPtr caps) { static const char *machine[] = { "g3beige", "mac99", "prep", "ppce500" }; virCapsGuestMachinePtr *machines = NULL; virCapsGuestPtr guest; machines = virCapabilitiesAllocMachines(machine, 1); if (!machines) goto error; guest = virCapabilitiesAddGuest(caps, VIR_DOMAIN_OSTYPE_HVM, VIR_ARCH_PPC, QEMUBinList[TEST_UTILS_QEMU_BIN_PPC], NULL, 1, machines); if (!guest) goto error; if (!virCapabilitiesAddGuestDomain(guest, VIR_DOMAIN_VIRT_QEMU, NULL, NULL, 0, NULL)) goto error; if (!virCapabilitiesAddGuestDomain(guest, VIR_DOMAIN_VIRT_KVM, NULL, NULL, 0, NULL)) goto error; return 0; error: /* No way to free a guest? */ virCapabilitiesFreeMachines(machines, 1); return -1; } static int testQemuAddRISCV32Guest(virCapsPtr caps) { static const char *names[] = { "spike_v1.10", "spike_v1.9.1", "sifive_e", "virt", "sifive_u" }; static const int nmachines = ARRAY_CARDINALITY(names); virCapsGuestMachinePtr *machines = NULL; virCapsGuestPtr guest; machines = virCapabilitiesAllocMachines(names, nmachines); if (!machines) goto error; guest = virCapabilitiesAddGuest(caps, VIR_DOMAIN_OSTYPE_HVM, VIR_ARCH_RISCV32, QEMUBinList[TEST_UTILS_QEMU_BIN_RISCV32], NULL, nmachines, machines); if (!guest) goto error; if (!virCapabilitiesAddGuestDomain(guest, VIR_DOMAIN_VIRT_QEMU, NULL, NULL, 0, NULL)) goto error; return 0; error: virCapabilitiesFreeMachines(machines, nmachines); return -1; } static int testQemuAddRISCV64Guest(virCapsPtr caps) { static const char *names[] = { "spike_v1.10", "spike_v1.9.1", "sifive_e", "virt", "sifive_u" }; static const int nmachines = ARRAY_CARDINALITY(names); virCapsGuestMachinePtr *machines = NULL; virCapsGuestPtr guest; machines = virCapabilitiesAllocMachines(names, nmachines); if (!machines) goto error; guest = virCapabilitiesAddGuest(caps, VIR_DOMAIN_OSTYPE_HVM, VIR_ARCH_RISCV64, QEMUBinList[TEST_UTILS_QEMU_BIN_RISCV64], NULL, nmachines, machines); if (!guest) goto error; if (!virCapabilitiesAddGuestDomain(guest, VIR_DOMAIN_VIRT_QEMU, NULL, NULL, 0, NULL)) goto error; return 0; error: virCapabilitiesFreeMachines(machines, nmachines); return -1; } static int testQemuAddS390Guest(virCapsPtr caps) { static const char *s390_machines[] = { "s390-virtio", "s390-ccw-virtio" }; virCapsGuestMachinePtr *machines = NULL; virCapsGuestPtr guest; machines = virCapabilitiesAllocMachines(s390_machines, ARRAY_CARDINALITY(s390_machines)); if (!machines) goto error; guest = virCapabilitiesAddGuest(caps, VIR_DOMAIN_OSTYPE_HVM, VIR_ARCH_S390X, QEMUBinList[TEST_UTILS_QEMU_BIN_S390X], NULL, ARRAY_CARDINALITY(s390_machines), machines); if (!guest) goto error; if (!virCapabilitiesAddGuestDomain(guest, VIR_DOMAIN_VIRT_QEMU, NULL, NULL, 0, NULL)) goto error; if (!virCapabilitiesAddGuestDomain(guest, VIR_DOMAIN_VIRT_KVM, NULL, NULL, 0, NULL)) goto error; return 0; error: virCapabilitiesFreeMachines(machines, ARRAY_CARDINALITY(s390_machines)); return -1; } static int testQemuAddArm6Guest(virCapsPtr caps) { static const char *machines[] = { "versatilepb" }; virCapsGuestMachinePtr *capsmachines = NULL; virCapsGuestPtr guest; capsmachines = virCapabilitiesAllocMachines(machines, ARRAY_CARDINALITY(machines)); if (!capsmachines) goto error; guest = virCapabilitiesAddGuest(caps, VIR_DOMAIN_OSTYPE_HVM, VIR_ARCH_ARMV6L, QEMUBinList[TEST_UTILS_QEMU_BIN_ARM], NULL, ARRAY_CARDINALITY(machines), capsmachines); if (!guest) goto error; if (!virCapabilitiesAddGuestDomain(guest, VIR_DOMAIN_VIRT_QEMU, NULL, NULL, 0, NULL)) goto error; if (!virCapabilitiesAddGuestDomain(guest, VIR_DOMAIN_VIRT_KVM, NULL, NULL, 0, NULL)) goto error; return 0; error: virCapabilitiesFreeMachines(capsmachines, ARRAY_CARDINALITY(machines)); return -1; } static int testQemuAddArm7Guest(virCapsPtr caps) { static const char *machines[] = { "vexpress-a9", "vexpress-a15", "versatilepb" }; virCapsGuestMachinePtr *capsmachines = NULL; virCapsGuestPtr guest; capsmachines = virCapabilitiesAllocMachines(machines, ARRAY_CARDINALITY(machines)); if (!capsmachines) goto error; guest = virCapabilitiesAddGuest(caps, VIR_DOMAIN_OSTYPE_HVM, VIR_ARCH_ARMV7L, QEMUBinList[TEST_UTILS_QEMU_BIN_ARM], NULL, ARRAY_CARDINALITY(machines), capsmachines); if (!guest) goto error; if (!virCapabilitiesAddGuestDomain(guest, VIR_DOMAIN_VIRT_QEMU, NULL, NULL, 0, NULL)) goto error; if (!virCapabilitiesAddGuestDomain(guest, VIR_DOMAIN_VIRT_KVM, NULL, NULL, 0, NULL)) goto error; return 0; error: virCapabilitiesFreeMachines(capsmachines, ARRAY_CARDINALITY(machines)); return -1; } static int testQemuAddAARCH64Guest(virCapsPtr caps) { static const char *machines[] = { "virt"}; virCapsGuestMachinePtr *capsmachines = NULL; virCapsGuestPtr guest; capsmachines = virCapabilitiesAllocMachines(machines, ARRAY_CARDINALITY(machines)); if (!capsmachines) goto error; guest = virCapabilitiesAddGuest(caps, VIR_DOMAIN_OSTYPE_HVM, VIR_ARCH_AARCH64, QEMUBinList[TEST_UTILS_QEMU_BIN_AARCH64], NULL, ARRAY_CARDINALITY(machines), capsmachines); if (!guest) goto error; if (!virCapabilitiesAddGuestDomain(guest, VIR_DOMAIN_VIRT_QEMU, NULL, NULL, 0, NULL)) goto error; if (!virCapabilitiesAddGuestDomain(guest, VIR_DOMAIN_VIRT_KVM, NULL, NULL, 0, NULL)) goto error; return 0; error: virCapabilitiesFreeMachines(capsmachines, ARRAY_CARDINALITY(machines)); return -1; } virCapsPtr testQemuCapsInit(void) { virCapsPtr caps; if (!(caps = virCapabilitiesNew(VIR_ARCH_X86_64, false, false))) return NULL; /* Add dummy 'none' security_driver. This is equal to setting * security_driver = "none" in qemu.conf. */ if (VIR_ALLOC_N(caps->host.secModels, 1) < 0) goto cleanup; caps->host.nsecModels = 1; if (VIR_STRDUP(caps->host.secModels[0].model, "none") < 0 || VIR_STRDUP(caps->host.secModels[0].doi, "0") < 0) goto cleanup; if (!(cpuDefault = virCPUDefCopy(&cpuDefaultData)) || !(cpuHaswell = virCPUDefCopy(&cpuHaswellData)) || !(cpuPower8 = virCPUDefCopy(&cpuPower8Data)) || !(cpuPower9 = virCPUDefCopy(&cpuPower9Data))) goto cleanup; qemuTestSetHostCPU(caps, NULL); /* * Build a NUMA topology with cell_id (NUMA node id * being 3(0 + 3),4(1 + 3), 5 and 6 */ if (virTestCapsBuildNUMATopology(caps, 3) < 0) goto cleanup; if (testQemuAddI686Guest(caps) < 0) goto cleanup; if (testQemuAddX86_64Guest(caps) < 0) goto cleanup; if (testQemuAddPPC64Guest(caps)) goto cleanup; if (testQemuAddPPC64LEGuest(caps)) goto cleanup; if (testQemuAddPPCGuest(caps)) goto cleanup; if (testQemuAddRISCV32Guest(caps) < 0) goto cleanup; if (testQemuAddRISCV64Guest(caps) < 0) goto cleanup; if (testQemuAddS390Guest(caps)) goto cleanup; if (testQemuAddArm6Guest(caps)) goto cleanup; if (testQemuAddArm7Guest(caps)) goto cleanup; if (testQemuAddAARCH64Guest(caps)) goto cleanup; if (virTestGetDebug()) { char *caps_str; caps_str = virCapabilitiesFormatXML(caps); if (!caps_str) goto cleanup; VIR_TEST_DEBUG("QEMU driver capabilities:\n%s", caps_str); VIR_FREE(caps_str); } return caps; cleanup: caps->host.cpu = NULL; virCPUDefFree(cpuDefault); virCPUDefFree(cpuHaswell); virCPUDefFree(cpuPower8); virObjectUnref(caps); return NULL; } void qemuTestSetHostArch(virCapsPtr caps, virArch arch) { if (arch == VIR_ARCH_NONE) arch = VIR_ARCH_X86_64; caps->host.arch = arch; qemuTestSetHostCPU(caps, NULL); } void qemuTestSetHostCPU(virCapsPtr caps, virCPUDefPtr cpu) { virArch arch = caps->host.arch; if (!cpu) { if (ARCH_IS_X86(arch)) cpu = cpuDefault; else if (ARCH_IS_PPC64(arch)) cpu = cpuPower8; } unsetenv("VIR_TEST_MOCK_FAKE_HOST_CPU"); if (cpu) { caps->host.arch = cpu->arch; if (cpu->model) setenv("VIR_TEST_MOCK_FAKE_HOST_CPU", cpu->model, 1); } caps->host.cpu = cpu; } virQEMUCapsPtr qemuTestParseCapabilitiesArch(virArch arch, const char *capsFile) { virQEMUCapsPtr qemuCaps = NULL; if (!(qemuCaps = virQEMUCapsNew()) || virQEMUCapsLoadCache(arch, qemuCaps, capsFile) < 0) goto error; return qemuCaps; error: virObjectUnref(qemuCaps); return NULL; } virQEMUCapsPtr qemuTestParseCapabilities(virCapsPtr caps, const char *capsFile) { if (!caps) return NULL; return qemuTestParseCapabilitiesArch(caps->host.arch, capsFile); } void qemuTestDriverFree(virQEMUDriver *driver) { virMutexDestroy(&driver->lock); if (driver->config) { virFileDeleteTree(driver->config->stateDir); virFileDeleteTree(driver->config->configDir); } virObjectUnref(driver->qemuCapsCache); virObjectUnref(driver->xmlopt); virObjectUnref(driver->caps); virObjectUnref(driver->config); virObjectUnref(driver->securityManager); } int qemuTestCapsCacheInsert(virFileCachePtr cache, virQEMUCapsPtr caps) { size_t i; virQEMUCapsPtr tmpCaps; if (caps) { tmpCaps = caps; } else { if (!(tmpCaps = virQEMUCapsNew())) return -ENOMEM; } for (i = 0; i < ARRAY_CARDINALITY(QEMUBinList); i++) { virObjectRef(tmpCaps); if (virFileCacheInsertData(cache, QEMUBinList[i], tmpCaps) < 0) { virObjectUnref(tmpCaps); return -1; } } if (!caps) virObjectUnref(tmpCaps); return 0; } # define STATEDIRTEMPLATE abs_builddir "/qemustatedir-XXXXXX" # define CONFIGDIRTEMPLATE abs_builddir "/qemuconfigdir-XXXXXX" int qemuTestDriverInit(virQEMUDriver *driver) { virSecurityManagerPtr mgr = NULL; char statedir[] = STATEDIRTEMPLATE; char configdir[] = CONFIGDIRTEMPLATE; memset(driver, 0, sizeof(*driver)); if (virMutexInit(&driver->lock) < 0) return -1; driver->config = virQEMUDriverConfigNew(false); if (!driver->config) goto error; /* Do this early so that qemuTestDriverFree() doesn't see (unlink) the real * dirs. */ VIR_FREE(driver->config->stateDir); VIR_FREE(driver->config->configDir); /* Overwrite some default paths so it's consistent for tests. */ VIR_FREE(driver->config->libDir); VIR_FREE(driver->config->channelTargetDir); if (VIR_STRDUP(driver->config->libDir, "/tmp/lib") < 0 || VIR_STRDUP(driver->config->channelTargetDir, "/tmp/channel") < 0) goto error; if (!mkdtemp(statedir)) { virFilePrintf(stderr, "Cannot create fake stateDir"); goto error; } if (VIR_STRDUP(driver->config->stateDir, statedir) < 0) { rmdir(statedir); goto error; } if (!mkdtemp(configdir)) { virFilePrintf(stderr, "Cannot create fake configDir"); goto error; } if (VIR_STRDUP(driver->config->configDir, configdir) < 0) { rmdir(configdir); goto error; } driver->caps = testQemuCapsInit(); if (!driver->caps) goto error; /* Using /dev/null for libDir and cacheDir automatically produces errors * upon attempt to use any of them */ driver->qemuCapsCache = virQEMUCapsCacheNew("/dev/null", "/dev/null", 0, 0, 0); if (!driver->qemuCapsCache) goto error; driver->xmlopt = virQEMUDriverCreateXMLConf(driver); if (!driver->xmlopt) goto error; if (qemuTestCapsCacheInsert(driver->qemuCapsCache, NULL) < 0) goto error; if (!(mgr = virSecurityManagerNew("none", "qemu", VIR_SECURITY_MANAGER_PRIVILEGED))) goto error; if (!(driver->securityManager = virSecurityManagerNewStack(mgr))) goto error; return 0; error: virObjectUnref(mgr); qemuTestDriverFree(driver); return -1; } int testQemuCapsSetGIC(virQEMUCapsPtr qemuCaps, int gic) { virGICCapability *gicCapabilities = NULL; size_t ngicCapabilities = 0; int ret = -1; if (VIR_ALLOC_N(gicCapabilities, 2) < 0) goto out; # define IMPL_BOTH \ VIR_GIC_IMPLEMENTATION_KERNEL|VIR_GIC_IMPLEMENTATION_EMULATED if (gic & GIC_V2) { gicCapabilities[ngicCapabilities].version = VIR_GIC_VERSION_2; gicCapabilities[ngicCapabilities].implementation = IMPL_BOTH; ngicCapabilities++; } if (gic & GIC_V3) { gicCapabilities[ngicCapabilities].version = VIR_GIC_VERSION_3; gicCapabilities[ngicCapabilities].implementation = IMPL_BOTH; ngicCapabilities++; } # undef IMPL_BOTH virQEMUCapsSetGICCapabilities(qemuCaps, gicCapabilities, ngicCapabilities); ret = 0; out: return ret; } #endif char * testQemuGetLatestCapsForArch(const char *dirname, const char *arch, const char *suffix) { struct dirent *ent; DIR *dir = NULL; int rc; char *fullsuffix = NULL; char *tmp = NULL; unsigned long maxver = 0; unsigned long ver; const char *maxname = NULL; char *ret = NULL; if (virAsprintf(&fullsuffix, "%s.%s", arch, suffix) < 0) goto cleanup; if (virDirOpen(&dir, dirname) < 0) goto cleanup; while ((rc = virDirRead(dir, &ent, dirname)) > 0) { VIR_FREE(tmp); if ((rc = VIR_STRDUP(tmp, STRSKIP(ent->d_name, "caps_"))) < 0) goto cleanup; if (rc == 0) continue; if (!virStringStripSuffix(tmp, fullsuffix)) continue; if (virParseVersionString(tmp, &ver, false) < 0) { VIR_TEST_DEBUG("skipping caps file '%s'\n", ent->d_name); continue; } if (ver > maxver) { maxname = ent->d_name; maxver = ver; } } if (rc < 0) goto cleanup; if (!maxname) { VIR_TEST_VERBOSE("failed to find capabilities for '%s' in '%s'\n", arch, dirname); goto cleanup; } ignore_value(virAsprintf(&ret, "%s/%s", dirname, maxname)); cleanup: VIR_FREE(tmp); VIR_FREE(fullsuffix); virDirClose(&dir); return ret; } int testQemuCapsIterate(const char *dirname, const char *suffix, testQemuCapsIterateCallback callback, void *opaque) { struct dirent *ent; DIR *dir = NULL; int rc; int ret = -1; if (!callback) return 0; if (virDirOpen(&dir, dirname) < 0) goto cleanup; while ((rc = virDirRead(dir, &ent, dirname) > 0)) { char *tmp = ent->d_name; char *base = NULL; char *archName = NULL; /* Strip the trailing suffix, moving on if it's not present */ if (!virStringStripSuffix(tmp, suffix)) continue; /* Find the last dot, moving on if none is present */ if (!(archName = strrchr(tmp, '.'))) continue; /* The base name is everything before the last dot, and * the architecture name everything after it */ base = tmp; archName[0] = '\0'; archName++; /* Run the user-provided callback */ if (callback(base, archName, opaque) < 0) goto cleanup; } if (rc < 0) goto cleanup; ret = 0; cleanup: virDirClose(&dir); return ret; }