diff --git a/src/esx/README b/src/esx/README index 9ae93b12c0..b7a5865cd8 100644 --- a/src/esx/README +++ b/src/esx/README @@ -86,3 +86,28 @@ It tries to cancel the blocked task, but this may not be possible, because there are task like the power-on task that is marked as non-cancelable. So the driver may leave blocked tasks behind if automatic question handling is disabled. + + + +Different escaping schemes used in different places +=================================================== + +A domain name in the vSphere API has [%/\] escaped as %XX (percent-encoding), +where XX is the ASCII code of the escaped char in hex. + +A domainName entry in a VMX config file is percent-encoded and has [|"] escaped +as |XX (pipe-encoding). + +A annotation entry in a VMX config file is pipe-encoded. + +A datastore item name has the special Windows path characters ["*<>:|?] +replaced by underscores (_). The result is escaped using percent-encoding and +base64-encoding. This isn't a bijective encoding. Therefore, escaped datastore +item names cannot be unescaped completely. + +For base64-encoding sequences of chars that don't match [a-zA-Z0-9'(),. _-] +are replaced by their base64 form (the padding is omitted). An encoded sequence +begins with a plus (+), ends with a minus (-) and can contain a plus (+). The +minus (-) is omitted if the string ends in a base64-encoded sequence. VMware +uses the comma (,) instead of the slash (/) in the base64 alphabet to avoid +conflicts with the slash as path separator. diff --git a/src/esx/esx_driver.c b/src/esx/esx_driver.c index e959be2883..8bc3be29fa 100644 --- a/src/esx/esx_driver.c +++ b/src/esx/esx_driver.c @@ -2708,7 +2708,6 @@ esxListDefinedDomains(virConnectPtr conn, char **const names, int maxnames) esxVI_String *propertyNameList = NULL; esxVI_ObjectContent *virtualMachineList = NULL; esxVI_ObjectContent *virtualMachine = NULL; - esxVI_DynamicProperty *dynamicProperty = NULL; esxVI_VirtualMachinePowerState powerState; int count = 0; int i; @@ -2745,27 +2744,15 @@ esxListDefinedDomains(virConnectPtr conn, char **const names, int maxnames) continue; } - for (dynamicProperty = virtualMachine->propSet; - dynamicProperty != NULL; - dynamicProperty = dynamicProperty->_next) { - if (STREQ(dynamicProperty->name, "name")) { - if (esxVI_AnyType_ExpectType(dynamicProperty->val, - esxVI_Type_String) < 0) { - goto cleanup; - } + names[count] = NULL; - names[count] = strdup(dynamicProperty->val->string); - - if (names[count] == NULL) { - virReportOOMError(); - goto cleanup; - } - - count++; - break; - } + if (esxVI_GetVirtualMachineIdentity(virtualMachine, NULL, &names[count], + NULL) < 0) { + goto cleanup; } + ++count; + if (count >= maxnames) { break; } @@ -2864,14 +2851,18 @@ esxDomainCreateWithFlags(virDomainPtr domain, unsigned int flags) return result; } + + static int esxDomainCreate(virDomainPtr domain) { return esxDomainCreateWithFlags(domain, 0); } + + static virDomainPtr -esxDomainDefineXML(virConnectPtr conn, const char *xml ATTRIBUTE_UNUSED) +esxDomainDefineXML(virConnectPtr conn, const char *xml) { esxPrivate *priv = conn->privateData; virDomainDefPtr def = NULL; @@ -2883,6 +2874,7 @@ esxDomainDefineXML(virConnectPtr conn, const char *xml ATTRIBUTE_UNUSED) esxVMX_Data data; char *datastoreName = NULL; char *directoryName = NULL; + char *escapedName = NULL; virBuffer buffer = VIR_BUFFER_INITIALIZER; char *url = NULL; char *datastoreRelatedPath = NULL; @@ -2912,6 +2904,13 @@ esxDomainDefineXML(virConnectPtr conn, const char *xml ATTRIBUTE_UNUSED) goto cleanup; } + if (virtualMachine == NULL && + esxVI_LookupVirtualMachineByName(priv->primary, def->name, NULL, + &virtualMachine, + esxVI_Occurrence_OptionalItem) < 0) { + goto cleanup; + } + if (virtualMachine != NULL) { /* FIXME */ ESX_ERROR(VIR_ERR_INTERNAL_ERROR, "%s", @@ -2992,7 +2991,13 @@ esxDomainDefineXML(virConnectPtr conn, const char *xml ATTRIBUTE_UNUSED) virBufferAddChar(&buffer, '/'); } - virBufferURIEncodeString(&buffer, def->name); + escapedName = esxUtil_EscapeDatastoreItem(def->name); + + if (escapedName == NULL) { + goto cleanup; + } + + virBufferURIEncodeString(&buffer, escapedName); virBufferAddLit(&buffer, ".vmx?dcPath="); virBufferURIEncodeString(&buffer, priv->primary->datacenter->name); virBufferAddLit(&buffer, "&dsName="); @@ -3005,29 +3010,31 @@ esxDomainDefineXML(virConnectPtr conn, const char *xml ATTRIBUTE_UNUSED) url = virBufferContentAndReset(&buffer); - if (directoryName != NULL) { - if (virAsprintf(&datastoreRelatedPath, "[%s] %s/%s.vmx", datastoreName, - directoryName, def->name) < 0) { - virReportOOMError(); - goto cleanup; - } - } else { - if (virAsprintf(&datastoreRelatedPath, "[%s] %s.vmx", datastoreName, - def->name) < 0) { - virReportOOMError(); - goto cleanup; - } - } - /* Check, if VMX file already exists */ /* FIXME */ /* Upload VMX file */ + VIR_DEBUG("Uploading .vmx config, url='%s' vmx='%s'", url, vmx); + if (esxVI_Context_UploadFile(priv->primary, url, vmx) < 0) { goto cleanup; } /* Register the domain */ + if (directoryName != NULL) { + if (virAsprintf(&datastoreRelatedPath, "[%s] %s/%s.vmx", datastoreName, + directoryName, escapedName) < 0) { + virReportOOMError(); + goto cleanup; + } + } else { + if (virAsprintf(&datastoreRelatedPath, "[%s] %s.vmx", datastoreName, + escapedName) < 0) { + virReportOOMError(); + goto cleanup; + } + } + if (esxVI_RegisterVM_Task(priv->primary, priv->primary->datacenter->vmFolder, datastoreRelatedPath, NULL, esxVI_Boolean_False, priv->primary->computeResource->resourcePool, @@ -3061,6 +3068,7 @@ esxDomainDefineXML(virConnectPtr conn, const char *xml ATTRIBUTE_UNUSED) VIR_FREE(vmx); VIR_FREE(datastoreName); VIR_FREE(directoryName); + VIR_FREE(escapedName); VIR_FREE(url); VIR_FREE(datastoreRelatedPath); esxVI_ObjectContent_Free(&virtualMachine); diff --git a/src/esx/esx_storage_driver.c b/src/esx/esx_storage_driver.c index 329020b065..72e0d7a5cd 100644 --- a/src/esx/esx_storage_driver.c +++ b/src/esx/esx_storage_driver.c @@ -915,9 +915,13 @@ esxStorageVolumeCreateXML(virStoragePoolPtr pool, const char *xmldesc, virStoragePoolDef poolDef; virStorageVolDefPtr def = NULL; char *tmp; - char *datastorePath = NULL; + char *unescapedDatastorePath = NULL; + char *unescapedDirectoryName = NULL; + char *unescapedDirectoryAndFileName = NULL; char *directoryName = NULL; + char *fileName = NULL; char *datastorePathWithoutFileName = NULL; + char *datastorePath = NULL; esxVI_FileInfo *fileInfo = NULL; esxVI_FileBackedVirtualDiskSpec *virtualDiskSpec = NULL; esxVI_ManagedObjectReference *task = NULL; @@ -995,15 +999,30 @@ esxStorageVolumeCreateXML(virStoragePoolPtr pool, const char *xmldesc, goto cleanup; } - if (virAsprintf(&datastorePath, "[%s] %s", pool->name, def->name) < 0) { + if (virAsprintf(&unescapedDatastorePath, "[%s] %s", pool->name, + def->name) < 0) { virReportOOMError(); goto cleanup; } if (def->target.format == VIR_STORAGE_FILE_VMDK) { - /* Create directory, if it doesn't exist yet */ - if (esxUtil_ParseDatastorePath(datastorePath, NULL, &directoryName, - NULL) < 0) { + /* Parse and escape datastore path */ + if (esxUtil_ParseDatastorePath(unescapedDatastorePath, NULL, + &unescapedDirectoryName, + &unescapedDirectoryAndFileName) < 0) { + goto cleanup; + } + + directoryName = esxUtil_EscapeDatastoreItem(unescapedDirectoryName); + + if (directoryName == NULL) { + goto cleanup; + } + + fileName = esxUtil_EscapeDatastoreItem(unescapedDirectoryAndFileName + + strlen(unescapedDirectoryName) + 1); + + if (fileName == NULL) { goto cleanup; } @@ -1013,6 +1032,13 @@ esxStorageVolumeCreateXML(virStoragePoolPtr pool, const char *xmldesc, goto cleanup; } + if (virAsprintf(&datastorePath, "[%s] %s/%s", pool->name, directoryName, + fileName) < 0) { + virReportOOMError(); + goto cleanup; + } + + /* Create directory, if it doesn't exist yet */ if (esxVI_LookupFileInfoByDatastorePath (priv->primary, datastorePathWithoutFileName, true, &fileInfo, esxVI_Occurrence_OptionalItem) < 0) { @@ -1115,9 +1141,13 @@ esxStorageVolumeCreateXML(virStoragePoolPtr pool, const char *xmldesc, esxVI_ObjectContent_Free(&datastore); esxVI_DatastoreInfo_Free(&datastoreInfo); virStorageVolDefFree(def); - VIR_FREE(datastorePath); + VIR_FREE(unescapedDatastorePath); + VIR_FREE(unescapedDirectoryName); + VIR_FREE(unescapedDirectoryAndFileName); VIR_FREE(directoryName); + VIR_FREE(fileName); VIR_FREE(datastorePathWithoutFileName); + VIR_FREE(datastorePath); esxVI_FileInfo_Free(&fileInfo); esxVI_FileBackedVirtualDiskSpec_Free(&virtualDiskSpec); esxVI_ManagedObjectReference_Free(&task); diff --git a/src/esx/esx_util.c b/src/esx/esx_util.c index 99cc4f6b53..5fe72fcc18 100644 --- a/src/esx/esx_util.c +++ b/src/esx/esx_util.c @@ -24,6 +24,7 @@ #include +#include #include #include "internal.h" @@ -621,3 +622,200 @@ esxUtil_ReformatUuid(const char *input, char *output) return 0; } + + + +char * +esxUtil_EscapeHex(const char *string, char escape, const char *special) +{ + char *escaped = NULL; + size_t length = 1; /* 1 byte for termination */ + const char *tmp1 = string; + char *tmp2; + + /* Calculate length of escaped string */ + while (*tmp1 != '\0') { + if (*tmp1 == escape || strspn(tmp1, special) > 0) { + length += 2; + } + + ++tmp1; + ++length; + } + + if (VIR_ALLOC_N(escaped, length) < 0) { + virReportOOMError(); + return NULL; + } + + tmp1 = string; /* reading from this one */ + tmp2 = escaped; /* writing to this one */ + + /* Escape to 'cXX' where c is the escape char and X is a hex digit */ + while (*tmp1 != '\0') { + if (*tmp1 == escape || strspn(tmp1, special) > 0) { + *tmp2++ = escape; + + snprintf(tmp2, 3, "%02x", (unsigned int)*tmp1); + + tmp2 += 2; + } else { + *tmp2++ = *tmp1; + } + + ++tmp1; + } + + *tmp2 = '\0'; + + return escaped; +} + + + +int +esxUtil_UnescapeHex(char *string, char escape) +{ + char *tmp1 = string; /* reading from this one */ + char *tmp2 = string; /* writing to this one */ + + /* Unescape from 'cXX' where c is the escape char and X is a hex digit */ + while (*tmp1 != '\0') { + if (*tmp1 == escape) { + if (!c_isxdigit(tmp1[1]) || !c_isxdigit(tmp1[2])) { + return -1; + } + + *tmp2++ = virHexToBin(tmp1[1]) * 16 + virHexToBin(tmp1[2]); + tmp1 += 3; + } else { + *tmp2++ = *tmp1++; + } + } + + *tmp2 = '\0'; + + return 0; +} + + + +char * +esxUtil_EscapeBase64(const char *string) +{ + /* 'normal' characters don't get base64 encoded */ + static const char *normal = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'(),. _-"; + + /* VMware uses ',' instead of the path separator '/' in the base64 alphabet */ + static const char *base64 = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,"; + + virBuffer buffer = VIR_BUFFER_INITIALIZER; + const char *tmp1 = string; + size_t length; + unsigned char c1, c2, c3; + + /* Escape sequences of non-'normal' characters as base64 without padding */ + while (*tmp1 != '\0') { + length = strspn(tmp1, normal); + + if (length > 0) { + virBufferAdd(&buffer, tmp1, length); + + tmp1 += length; + } else { + length = strcspn(tmp1, normal); + + virBufferAddChar(&buffer, '+'); + + while (length > 0) { + c1 = *tmp1++; + c2 = length > 1 ? *tmp1++ : 0; + c3 = length > 2 ? *tmp1++ : 0; + + virBufferAddChar(&buffer, base64[(c1 >> 2) & 0x3f]); + virBufferAddChar(&buffer, base64[((c1 << 4) + (c2 >> 4)) & 0x3f]); + + if (length > 1) { + virBufferAddChar(&buffer, base64[((c2 << 2) + (c3 >> 6)) & 0x3f]); + } + + if (length > 2) { + virBufferAddChar(&buffer, base64[c3 & 0x3f]); + } + + length -= length > 3 ? 3 : length; + } + + if (*tmp1 != '\0') { + virBufferAddChar(&buffer, '-'); + } + } + } + + if (virBufferError(&buffer)) { + virReportOOMError(); + virBufferFreeAndReset(&buffer); + + return NULL; + } + + return virBufferContentAndReset(&buffer); +} + + + +void +esxUtil_ReplaceSpecialWindowsPathChars(char *string) +{ + /* '/' and '\\' are missing on purpose */ + static const char *specials = "\"*<>:|?"; + + char *tmp = string; + size_t length; + + while (*tmp != '\0') { + length = strspn(tmp, specials); + + while (length > 0) { + *tmp++ = '_'; + --length; + } + + if (*tmp != '\0') { + ++tmp; + } + } +} + + + +char * +esxUtil_EscapeDatastoreItem(const char *string) +{ + char *replaced = strdup(string); + char *escaped1; + char *escaped2 = NULL; + + if (replaced == NULL) { + virReportOOMError(); + return NULL; + } + + esxUtil_ReplaceSpecialWindowsPathChars(replaced); + + escaped1 = esxUtil_EscapeHexPercent(replaced); + + if (escaped1 == NULL) { + goto cleanup; + } + + escaped2 = esxUtil_EscapeBase64(escaped1); + + cleanup: + VIR_FREE(replaced); + VIR_FREE(escaped1); + + return escaped2; +} diff --git a/src/esx/esx_util.h b/src/esx/esx_util.h index 650f14536b..669a4f23d4 100644 --- a/src/esx/esx_util.h +++ b/src/esx/esx_util.h @@ -71,4 +71,22 @@ int esxUtil_GetConfigBoolean(virConfPtr conf, const char *name, bool *boolean_, int esxUtil_ReformatUuid(const char *input, char *output); +char *esxUtil_EscapeHex(const char *string, char escape, const char *special); + +# define esxUtil_EscapeHexPipe(_string) esxUtil_EscapeHex(_string, '|', "\"") + +# define esxUtil_EscapeHexPercent(_string) esxUtil_EscapeHex(_string, '%', "/\\") + +int esxUtil_UnescapeHex(char *string, char escape); + +# define esxUtil_UnescapeHexPipe(_string) esxUtil_UnescapeHex(_string, '|') + +# define esxUtil_UnescapeHexPercent(_string) esxUtil_UnescapeHex(_string, '%') + +char *esxUtil_EscapeBase64(const char *string); + +void esxUtil_ReplaceSpecialWindowsPathChars(char *string); + +char *esxUtil_EscapeDatastoreItem(const char *string); + #endif /* __ESX_UTIL_H__ */ diff --git a/src/esx/esx_vi.c b/src/esx/esx_vi.c index d361f01931..c450730219 100644 --- a/src/esx/esx_vi.c +++ b/src/esx/esx_vi.c @@ -2014,6 +2014,12 @@ esxVI_GetVirtualMachineIdentity(esxVI_ObjectContent *virtualMachine, goto failure; } + if (esxUtil_UnescapeHexPercent(*name) < 0) { + ESX_VI_ERROR(VIR_ERR_INTERNAL_ERROR, "%s", + _("Domain name contains invalid escape sequence")); + goto failure; + } + break; } } diff --git a/src/esx/esx_vmx.c b/src/esx/esx_vmx.c index ece16b206a..e17e1e704a 100644 --- a/src/esx/esx_vmx.c +++ b/src/esx/esx_vmx.c @@ -876,8 +876,6 @@ esxVMX_ParseConfig(esxVMX_Context *ctx, virCapsPtr caps, const char *vmx, long long numvcpus = 0; char *sched_cpu_affinity = NULL; char *guestOS = NULL; - char *tmp1; - char *tmp2; int controller; int bus; int port; @@ -982,34 +980,28 @@ esxVMX_ParseConfig(esxVMX_Context *ctx, virCapsPtr caps, const char *vmx, goto cleanup; } + if (def->name != NULL) { + if (esxUtil_UnescapeHexPercent(def->name) < 0 || + esxUtil_UnescapeHexPipe(def->name) < 0) { + ESX_ERROR(VIR_ERR_INTERNAL_ERROR, "%s", + _("VMX entry 'name' contains invalid escape sequence")); + goto cleanup; + } + } + /* vmx:annotation -> def:description */ if (esxUtil_GetConfigString(conf, "annotation", &def->description, true) < 0) { goto cleanup; } - /* Unescape '|XX' where X is a hex digit */ if (def->description != NULL) { - tmp1 = def->description; /* reading from this one */ - tmp2 = def->description; /* writing to this one */ - - while (*tmp1 != '\0') { - if (*tmp1 == '|') { - if (!c_isxdigit(tmp1[1]) || !c_isxdigit(tmp1[2])) { - ESX_ERROR(VIR_ERR_INTERNAL_ERROR, "%s", - _("VMX entry 'annotation' contains invalid " - "escape sequence")); - goto cleanup; - } - - *tmp2++ = virHexToBin(tmp1[1]) * 16 + virHexToBin(tmp1[2]); - tmp1 += 3; - } else { - *tmp2++ = *tmp1++; - } + if (esxUtil_UnescapeHexPipe(def->description) < 0) { + ESX_ERROR(VIR_ERR_INTERNAL_ERROR, "%s", + _("VMX entry 'annotation' contains invalid escape " + "sequence")); + goto cleanup; } - - *tmp2 = '\0'; } /* vmx:memsize -> def:mem.max_balloon */ @@ -2460,9 +2452,8 @@ esxVMX_FormatConfig(esxVMX_Context *ctx, virCapsPtr caps, virDomainDefPtr def, int sched_cpu_affinity_length; unsigned char zero[VIR_UUID_BUFLEN]; virBuffer buffer = VIR_BUFFER_INITIALIZER; - size_t length; - char *tmp1; - char *tmp2; + char *preliminaryDisplayName = NULL; + char *displayName = NULL; char *annotation = NULL; bool scsi_present[4] = { false, false, false, false }; int scsi_virtualDev[4] = { -1, -1, -1, -1 }; @@ -2536,44 +2527,23 @@ esxVMX_FormatConfig(esxVMX_Context *ctx, virCapsPtr caps, virDomainDefPtr def, } /* def:name -> vmx:displayName */ - virBufferVSprintf(&buffer, "displayName = \"%s\"\n", def->name); + preliminaryDisplayName = esxUtil_EscapeHexPipe(def->name); + + if (preliminaryDisplayName == NULL) { + goto cleanup; + } + + displayName = esxUtil_EscapeHexPercent(preliminaryDisplayName); + + if (displayName == NULL) { + goto cleanup; + } + + virBufferVSprintf(&buffer, "displayName = \"%s\"\n", displayName); /* def:description -> vmx:annotation */ if (def->description != NULL) { - /* Escape '"' as '|22' and '|' as '|7C' */ - length = 1; /* 1 byte for termination */ - tmp1 = def->description; - - while (*tmp1 != '\0') { - if (*tmp1 == '"' || *tmp1 == '|') { - length += 2; - } - - ++tmp1; - ++length; - } - - if (VIR_ALLOC_N(annotation, length) < 0) { - virReportOOMError(); - goto cleanup; - } - - tmp1 = def->description; /* reading from this one */ - tmp2 = annotation; /* writing to this one */ - - while (*tmp1 != '\0') { - if (*tmp1 == '"') { - *tmp2++ = '|'; *tmp2++ = '2'; *tmp2++ = '2'; - } else if (*tmp1 == '|') { - *tmp2++ = '|'; *tmp2++ = '7'; *tmp2++ = 'C'; - } else { - *tmp2++ = *tmp1; - } - - ++tmp1; - } - - *tmp2 = '\0'; + annotation = esxUtil_EscapeHexPipe(def->description); virBufferVSprintf(&buffer, "annotation = \"%s\"\n", annotation); } @@ -2780,6 +2750,8 @@ esxVMX_FormatConfig(esxVMX_Context *ctx, virCapsPtr caps, virDomainDefPtr def, virBufferFreeAndReset(&buffer); } + VIR_FREE(preliminaryDisplayName); + VIR_FREE(displayName); VIR_FREE(annotation); return vmx; diff --git a/tests/esxutilstest.c b/tests/esxutilstest.c index 4906ad41f4..d4042c2c2e 100644 --- a/tests/esxutilstest.c +++ b/tests/esxutilstest.c @@ -227,6 +227,59 @@ testConvertDateTimeToCalendarTime(const void *data ATTRIBUTE_UNUSED) +struct testDatastoreItem { + const char *string; + const char *escaped; +}; + +static struct testDatastoreItem datastoreItems[] = { + { "normal", "normal" }, + { /* "Aä1ö2ü3ß4#5~6!7§8/9%Z" */ + "A\303\2441\303\2662\303\2743\303\2374#5~6!7\302\2478/9%Z", + "A+w6Q-1+w7Y-2+w7w-3+w58-4+Iw-5+fg-6+IQ-7+wqc-8+JQ-2f9+JQ-25Z" }, + { /* "Z~6!7§8/9%0#1\"2'3`4&A" */ "Z~6!7\302\2478/9%0#1\"2'3`4&A", + "Z+fg-6+IQ-7+wqc-8+JQ-2f9+JQ-250+Iw-1_2'3+YA-4+Jg-A" }, + { /* "標準語" */ "\346\250\231\346\272\226\350\252\236", "+5qiZ5rqW6Kqe" }, + { "!\"#$%&'()*+,-./0123456789:;<=>?", + "+IQ-_+IyQl-25+Jg-'()_+Kw-,-.+JQ-2f0123456789_+Ow-_+PQ-__" }, + { "A Z[\\]^_B", "A Z+WyU-5c+XV4-_B" }, + { "A`B@{|}~DEL", "A+YA-B+QHs-_+fX4-DEL" }, + { /* "hÀÁÂÃÄÅH" */ "h\303\200\303\201\303\202\303\203\303\204\303\205H", + "h+w4DDgcOCw4PDhMOF-H" }, + { /* "A쿀Z" */ "A\354\277\200Z", "A+7L+A-Z" }, + { /* "!쿀A" */ "!\354\277\200A", "+Iey,gA-A" }, + { "~~~", "+fn5+" }, + { "~~~A", "+fn5+-A" }, + { "K%U/H\\Z", "K+JQ-25U+JQ-2fH+JQ-5cZ" }, + { "vvvzzz", "vvv_A_B_C_zzz" }, +}; + +static int +testEscapeDatastoreItem(const void *data ATTRIBUTE_UNUSED) +{ + int i; + char *escaped = NULL; + + for (i = 0; i < ARRAY_CARDINALITY(datastoreItems); ++i) { + VIR_FREE(escaped); + + escaped = esxUtil_EscapeDatastoreItem(datastoreItems[i].string); + + if (escaped == NULL) { + return -1; + } + + if (STRNEQ(datastoreItems[i].escaped, escaped)) { + VIR_FREE(escaped); + return -1; + } + } + + return 0; +} + + + static int mymain(int argc, char **argv) { @@ -258,6 +311,7 @@ mymain(int argc, char **argv) DO_TEST(DiskNameToIndex); DO_TEST(ParseDatastorePath); DO_TEST(ConvertDateTimeToCalendarTime); + DO_TEST(EscapeDatastoreItem); return result == 0 ? EXIT_SUCCESS : EXIT_FAILURE; } diff --git a/tests/xml2vmxdata/xml2vmx-annotation.vmx b/tests/xml2vmxdata/xml2vmx-annotation.vmx index 47d060db65..5754c3167b 100644 --- a/tests/xml2vmxdata/xml2vmx-annotation.vmx +++ b/tests/xml2vmxdata/xml2vmx-annotation.vmx @@ -3,7 +3,7 @@ virtualHW.version = "4" guestOS = "other" uuid.bios = "56 4d 9b ef ac d9 b4 e0-c8 f0 ae a8 b9 10 35 15" displayName = "annotation" -annotation = "Some |7Ctext|7C to test the |22escaping|22: |7C|7C|22|22|7C|7C|22|7C Unescaped!" +annotation = "Some |7ctext|7c to test the |22escaping|22: |7c|7c|22|22|7c|7c|22|7c Unescaped!" memsize = "4" numvcpus = "1" floppy0.present = "false"